Skip to content

Commit be71e99

Browse files
authored
Merge pull request #1410 from Adyen/fix/dr-1077
Use a dynamically-generated, random IV
2 parents ace4cc1 + f5e80e1 commit be71e99

File tree

1 file changed

+49
-12
lines changed

1 file changed

+49
-12
lines changed

src/main/java/com/adyen/terminal/security/NexoCrypto.java

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,37 +41,50 @@
4141
import java.security.InvalidKeyException;
4242
import java.security.MessageDigest;
4343
import java.security.NoSuchAlgorithmException;
44-
import java.security.Provider;
4544
import java.security.SecureRandom;
4645

4746
import static com.adyen.model.terminal.security.NexoDerivedKey.NEXO_IV_LENGTH;
4847

4948
public 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

Comments
 (0)