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}