4141import java .security .InvalidKeyException ;
4242import java .security .MessageDigest ;
4343import java .security .NoSuchAlgorithmException ;
44- import java .security .Provider ;
4544import java .security .SecureRandom ;
4645
4746import static com .adyen .model .terminal .security .NexoDerivedKey .NEXO_IV_LENGTH ;
4847
4948public class NexoCrypto {
5049
51- private static SecureRandom secureRandom = new SecureRandom ();
52- private static final Provider PROVIDER = secureRandom .getProvider ();
5350 private final SecurityKey securityKey ;
5451 private volatile NexoDerivedKey nexoDerivedKey ;
5552
5653 public NexoCrypto (SecurityKey securityKey ) throws NexoCryptoException {
54+ // Validate security key to ensure it has the necessary properties
5755 validateSecurityKey (securityKey );
5856 this .securityKey = securityKey ;
5957 }
6058
59+ /**
60+ * Encrypts the SaleToPOI message using the provided message header and security key.
61+ *
62+ * @param saleToPoiMessageJson the JSON string representing the SaleToPOI message
63+ * @param messageHeader the message header for encryption
64+ * @return encrypted SaleToPOISecuredMessage
65+ */
6166 public SaleToPOISecuredMessage encrypt (String saleToPoiMessageJson , MessageHeader messageHeader ) throws Exception {
6267 NexoDerivedKey derivedKey = getNexoDerivedKey ();
6368 byte [] saleToPoiMessageByteArray = saleToPoiMessageJson .getBytes (StandardCharsets .UTF_8 );
69+
70+ // Generate a random initialization vector (IV) nonce
6471 byte [] ivNonce = generateRandomIvNonce ();
72+
73+ // Perform AES encryption
6574 byte [] encryptedSaleToPoiMessage = crypt (saleToPoiMessageByteArray , derivedKey , ivNonce , Cipher .ENCRYPT_MODE );
75+
76+ // Generate HMAC for message authentication
6677 byte [] encryptedSaleToPoiMessageHmac = hmac (saleToPoiMessageByteArray , derivedKey );
6778
79+ // Populate security trailer with metadata and HMAC
6880 SecurityTrailer securityTrailer = new SecurityTrailer ();
6981 securityTrailer .setKeyVersion (securityKey .getKeyVersion ());
7082 securityTrailer .setKeyIdentifier (securityKey .getKeyIdentifier ());
7183 securityTrailer .setHmac (encryptedSaleToPoiMessageHmac );
7284 securityTrailer .setNonce (ivNonce );
7385 securityTrailer .setAdyenCryptoVersion (securityKey .getAdyenCryptoVersion ());
7486
87+ // Construct the secured message with the encrypted content and security trailer
7588 SaleToPOISecuredMessage saleToPoiSecuredMessage = new SaleToPOISecuredMessage ();
7689 saleToPoiSecuredMessage .setMessageHeader (messageHeader );
7790 saleToPoiSecuredMessage .setNexoBlob (new String (Base64 .encodeBase64 (encryptedSaleToPoiMessage )));
@@ -80,18 +93,37 @@ public SaleToPOISecuredMessage encrypt(String saleToPoiMessageJson, MessageHeade
8093 return saleToPoiSecuredMessage ;
8194 }
8295
96+ /**
97+ * Decrypts the SaleToPOI secured message.
98+ *
99+ * @param saleToPoiSecuredMessage the encrypted message
100+ * @return the decrypted SaleToPOI message as a JSON string
101+ */
83102 public String decrypt (SaleToPOISecuredMessage saleToPoiSecuredMessage ) throws Exception {
84103 NexoDerivedKey derivedKey = getNexoDerivedKey ();
104+
105+ // Decode the encrypted blob
85106 byte [] encryptedSaleToPoiMessageByteArray = Base64 .decodeBase64 (saleToPoiSecuredMessage .getNexoBlob ().getBytes ());
107+
108+ // Retrieve the nonce (IV) from the security trailer
86109 byte [] ivNonce = saleToPoiSecuredMessage .getSecurityTrailer ().getNonce ();
110+
111+ // Decrypt the message
87112 byte [] decryptedSaleToPoiMessageByteArray = crypt (encryptedSaleToPoiMessageByteArray , derivedKey , ivNonce , Cipher .DECRYPT_MODE );
88113
114+ // Validate HMAC to ensure message integrity
89115 byte [] receivedHmac = saleToPoiSecuredMessage .getSecurityTrailer ().getHmac ();
90116 validateHmac (receivedHmac , decryptedSaleToPoiMessageByteArray , derivedKey );
91117
92118 return new String (decryptedSaleToPoiMessageByteArray , StandardCharsets .UTF_8 );
93119 }
94120
121+ /**
122+ * Validates the security key to ensure all required fields are present.
123+ *
124+ * @param securityKey the security key to validate
125+ * @throws NexoCryptoException if the security key is invalid
126+ */
95127 private void validateSecurityKey (SecurityKey securityKey ) throws NexoCryptoException {
96128 if (securityKey == null
97129 || securityKey .getPassphrase () == null
@@ -103,6 +135,11 @@ private void validateSecurityKey(SecurityKey securityKey) throws NexoCryptoExcep
103135 }
104136 }
105137
138+ /**
139+ * Lazily initializes and retrieves the derived key material for encryption/decryption.
140+ *
141+ * @return the derived key material
142+ */
106143 private NexoDerivedKey getNexoDerivedKey () throws GeneralSecurityException {
107144 if (nexoDerivedKey == null ) {
108145 synchronized (this ) {
@@ -115,9 +152,7 @@ private NexoDerivedKey getNexoDerivedKey() throws GeneralSecurityException {
115152 }
116153
117154 /**
118- * Encrypt or decrypt data given a iv modifier and using the specified key
119- * <p>
120- * The actual iv is computed by taking the iv from the key material and xoring it with ivmod
155+ * Performs AES encryption/decryption using the derived key and provided IV.
121156 */
122157 private byte [] crypt (byte [] bytes , NexoDerivedKey dk , byte [] ivNonce , int mode )
123158 throws NoSuchAlgorithmException , NoSuchPaddingException ,
@@ -126,7 +161,7 @@ private byte[] crypt(byte[] bytes, NexoDerivedKey dk, byte[] ivNonce, int mode)
126161 Cipher cipher = Cipher .getInstance ("AES/CBC/PKCS5Padding" );
127162 SecretKeySpec secretKeySpec = new SecretKeySpec (dk .getCipherKey (), "AES" );
128163
129- // xor dk.iv and the iv modifier
164+ // Derive the actual IV by XORing the derived IV with the nonce
130165 byte [] iv = dk .getIv ();
131166 byte [] actualIV = new byte [NEXO_IV_LENGTH ];
132167 for (int i = 0 ; i < NEXO_IV_LENGTH ; i ++) {
@@ -139,7 +174,7 @@ private byte[] crypt(byte[] bytes, NexoDerivedKey dk, byte[] ivNonce, int mode)
139174 }
140175
141176 /**
142- * Compute a hmac using the hmacKey
177+ * Generates an HMAC for message authentication.
143178 */
144179 private byte [] hmac (byte [] bytes , NexoDerivedKey derivedKey ) throws NoSuchAlgorithmException , InvalidKeyException {
145180 Mac mac = Mac .getInstance ("HmacSHA256" );
@@ -150,25 +185,27 @@ private byte[] hmac(byte[] bytes, NexoDerivedKey derivedKey) throws NoSuchAlgori
150185 }
151186
152187 /**
153- * Validate the hmac from a received message
188+ * Validates the HMAC of a decrypted message to ensure data integrity.
154189 */
155190 private void validateHmac (byte [] receivedHmac , byte [] decryptedMessage , NexoDerivedKey derivedKey ) throws NexoCryptoException , InvalidKeyException , NoSuchAlgorithmException {
156191 byte [] hmac = hmac (decryptedMessage , derivedKey );
157192 boolean valid = MessageDigest .isEqual (hmac , receivedHmac );
158193
159194 if (!valid ) {
160- throw new NexoCryptoException ("Hmac validation failed" );
195+ throw new NexoCryptoException ("HMAC validation failed" );
161196 }
162197 }
163198
164199 /**
165- * Generate a random iv nonce with cryptographically strongest non blocking RNG
200+ * Generates a random IV nonce using a secure random number generator.
166201 */
167202 private byte [] generateRandomIvNonce () {
168203 byte [] ivNonce = new byte [NEXO_IV_LENGTH ];
204+ SecureRandom secureRandom ;
169205 try {
170- secureRandom = SecureRandom .getInstance ("NativePRNGNonBlocking" , PROVIDER );
206+ secureRandom = SecureRandom .getInstance ("NativePRNGNonBlocking" );
171207 } catch (Exception NoSuchAlgorithmException ) {
208+ // Fallback to default SecureRandom implementation
172209 secureRandom = new SecureRandom ();
173210 }
174211 secureRandom .nextBytes (ivNonce );
0 commit comments