001package com.pusher.rest.crypto;
002
003import com.pusher.rest.crypto.nacl.SecretBox;
004import com.pusher.rest.data.EncryptedMessage;
005import com.pusher.rest.util.Prerequisites;
006
007import java.nio.charset.StandardCharsets;
008import java.security.MessageDigest;
009import java.security.NoSuchAlgorithmException;
010import java.util.Base64;
011import java.util.Map;
012
013public class CryptoUtil {
014
015    private static final String SHARED_SECRET_ENCRYPTION_ALGO = "SHA-256";
016    private static final int MASTER_KEY_LENGTH = 32;
017    private final byte[] encryptionMasterKey;
018
019    public CryptoUtil(final String base64EncodedMasterKey) {
020        Prerequisites.nonEmpty("base64EncodedMasterKey", base64EncodedMasterKey);
021
022        this.encryptionMasterKey = parseEncryptionMasterKey(base64EncodedMasterKey);
023    }
024
025    public String generateBase64EncodedSharedSecret(final String channel) {
026        return Base64.getEncoder().withoutPadding().encodeToString(
027            generateSharedSecret(channel)
028        );
029    }
030
031    public EncryptedMessage encrypt(final String channel, final byte[] message) {
032        final byte[] sharedSecret = generateSharedSecret(channel);
033
034        final Map<String, byte[]> res = SecretBox.box(sharedSecret, message);
035
036        return new EncryptedMessage(
037            Base64.getEncoder().encodeToString(res.get("nonce")),
038            Base64.getEncoder().encodeToString(res.get("cipher"))
039        );
040    }
041
042    public String decrypt(final String channel, final EncryptedMessage encryptedMessage) {
043        final byte[] sharedSecret = generateSharedSecret(channel);
044
045        final byte[] decryptMessage = SecretBox.open(
046            sharedSecret,
047            Base64.getDecoder().decode(encryptedMessage.getNonce()),
048            Base64.getDecoder().decode(encryptedMessage.getCiphertext().getBytes())
049        );
050
051        return new String(decryptMessage, StandardCharsets.UTF_8);
052    }
053
054    private byte[] parseEncryptionMasterKey(final String base64EncodedEncryptionMasterKey) {
055        final byte[] key = Base64.getDecoder().decode(base64EncodedEncryptionMasterKey);
056
057        if (key.length != MASTER_KEY_LENGTH) {
058            throw new IllegalArgumentException("encryptionMasterKeyBase64 must be a 32 byte key, base64 encoded");
059        }
060
061        return key;
062    }
063
064    private byte[] generateSharedSecret(final String channel) {
065        try {
066            MessageDigest digest = MessageDigest.getInstance(CryptoUtil.SHARED_SECRET_ENCRYPTION_ALGO);
067            byte[] channelB = channel.getBytes(StandardCharsets.UTF_8);
068
069            byte[] buf = new byte[channelB.length + encryptionMasterKey.length];
070            System.arraycopy(channelB, 0, buf, 0, channelB.length);
071            System.arraycopy(encryptionMasterKey, 0, buf, channelB.length, encryptionMasterKey.length);
072
073            return digest.digest(buf);
074        } catch (NoSuchAlgorithmException e) {
075            throw new RuntimeException(e);
076        }
077    }
078}