Skip to content

Commit

Permalink
Improve comments
Browse files Browse the repository at this point in the history
  • Loading branch information
ayodejidev committed Dec 20, 2024
1 parent 5242529 commit 5af45ad
Showing 1 changed file with 54 additions and 0 deletions.
54 changes: 54 additions & 0 deletions src/main/java/com/adyen/terminal/security/NexoCrypto.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,24 +52,40 @@ public class NexoCrypto {
private volatile NexoDerivedKey nexoDerivedKey;

public NexoCrypto(SecurityKey securityKey) throws NexoCryptoException {
// Validate security key to ensure it has the necessary properties
validateSecurityKey(securityKey);
this.securityKey = securityKey;
}

/**
* Encrypts the SaleToPOI message using the provided message header and security key.
*
* @param saleToPoiMessageJson the JSON string representing the SaleToPOI message
* @param messageHeader the message header for encryption
* @return encrypted SaleToPOISecuredMessage
*/
public SaleToPOISecuredMessage encrypt(String saleToPoiMessageJson, MessageHeader messageHeader) throws GeneralSecurityException {
NexoDerivedKey derivedKey = getNexoDerivedKey();
byte[] saleToPoiMessageByteArray = saleToPoiMessageJson.getBytes(StandardCharsets.UTF_8);

// Generate a random initialization vector (IV) nonce
byte[] ivNonce = generateRandomIvNonce();

// Perform AES encryption
byte[] encryptedSaleToPoiMessage = crypt(saleToPoiMessageByteArray, derivedKey, ivNonce, Cipher.ENCRYPT_MODE);

// Generate HMAC for message authentication
byte[] encryptedSaleToPoiMessageHmac = hmac(saleToPoiMessageByteArray, derivedKey);

// Populate security trailer with metadata and HMAC
SecurityTrailer securityTrailer = new SecurityTrailer();
securityTrailer.setKeyVersion(securityKey.getKeyVersion());
securityTrailer.setKeyIdentifier(securityKey.getKeyIdentifier());
securityTrailer.setHmac(encryptedSaleToPoiMessageHmac);
securityTrailer.setNonce(ivNonce);
securityTrailer.setAdyenCryptoVersion(securityKey.getAdyenCryptoVersion());

// Construct the secured message with the encrypted content and security trailer
SaleToPOISecuredMessage saleToPoiSecuredMessage = new SaleToPOISecuredMessage();
saleToPoiSecuredMessage.setMessageHeader(messageHeader);
saleToPoiSecuredMessage.setNexoBlob(new String(Base64.encodeBase64(encryptedSaleToPoiMessage)));
Expand All @@ -78,18 +94,37 @@ public SaleToPOISecuredMessage encrypt(String saleToPoiMessageJson, MessageHeade
return saleToPoiSecuredMessage;
}

/**
* Decrypts the SaleToPOI secured message.
*
* @param saleToPoiSecuredMessage the encrypted message
* @return the decrypted SaleToPOI message as a JSON string
*/
public String decrypt(SaleToPOISecuredMessage saleToPoiSecuredMessage) throws GeneralSecurityException, NexoCryptoException {
NexoDerivedKey derivedKey = getNexoDerivedKey();

// Decode the encrypted blob
byte[] encryptedSaleToPoiMessageByteArray = Base64.decodeBase64(saleToPoiSecuredMessage.getNexoBlob().getBytes());

// Retrieve the nonce (IV) from the security trailer
byte[] ivNonce = saleToPoiSecuredMessage.getSecurityTrailer().getNonce();

// Decrypt the message
byte[] decryptedSaleToPoiMessageByteArray = crypt(encryptedSaleToPoiMessageByteArray, derivedKey, ivNonce, Cipher.DECRYPT_MODE);

// Validate HMAC to ensure message integrity
byte[] receivedHmac = saleToPoiSecuredMessage.getSecurityTrailer().getHmac();
validateHmac(receivedHmac, decryptedSaleToPoiMessageByteArray, derivedKey);

return new String(decryptedSaleToPoiMessageByteArray, StandardCharsets.UTF_8);
}

/**
* Validates the security key to ensure all required fields are present.
*
* @param securityKey the security key to validate
* @throws NexoCryptoException if the security key is invalid
*/
private void validateSecurityKey(SecurityKey securityKey) throws NexoCryptoException {
if (securityKey == null
|| securityKey.getPassphrase() == null
Expand All @@ -101,6 +136,11 @@ private void validateSecurityKey(SecurityKey securityKey) throws NexoCryptoExcep
}
}

/**
* Lazily initializes and retrieves the derived key material for encryption/decryption.
*
* @return the derived key material
*/
private NexoDerivedKey getNexoDerivedKey() throws GeneralSecurityException {
if (nexoDerivedKey == null) {
synchronized (this) {
Expand All @@ -112,13 +152,17 @@ private NexoDerivedKey getNexoDerivedKey() throws GeneralSecurityException {
return nexoDerivedKey;
}

/**
* Performs AES encryption/decryption using the derived key and provided IV.
*/
private byte[] crypt(byte[] bytes, NexoDerivedKey dk, byte[] ivNonce, int mode)
throws NoSuchAlgorithmException, NoSuchPaddingException,
IllegalBlockSizeException, BadPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec secretKeySpec = new SecretKeySpec(dk.getCipherKey(), "AES");

// Derive the actual IV by XORing the derived IV with the nonce
byte[] iv = dk.getIv();
byte[] actualIV = new byte[NEXO_IV_LENGTH];
for (int i = 0; i < NEXO_IV_LENGTH; i++) {
Expand All @@ -130,6 +174,9 @@ private byte[] crypt(byte[] bytes, NexoDerivedKey dk, byte[] ivNonce, int mode)
return cipher.doFinal(bytes);
}

/**
* Generates an HMAC for message authentication.
*/
private byte[] hmac(byte[] bytes, NexoDerivedKey derivedKey) throws NoSuchAlgorithmException, InvalidKeyException {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec s = new SecretKeySpec(derivedKey.getHmacKey(), "HmacSHA256");
Expand All @@ -138,6 +185,9 @@ private byte[] hmac(byte[] bytes, NexoDerivedKey derivedKey) throws NoSuchAlgori
return mac.doFinal(bytes);
}

/**
* Validates the HMAC of a decrypted message to ensure data integrity.
*/
private void validateHmac(byte[] receivedHmac, byte[] decryptedMessage, NexoDerivedKey derivedKey) throws NexoCryptoException, InvalidKeyException, NoSuchAlgorithmException {
byte[] hmac = hmac(decryptedMessage, derivedKey);
boolean valid = MessageDigest.isEqual(hmac, receivedHmac);
Expand All @@ -147,12 +197,16 @@ private void validateHmac(byte[] receivedHmac, byte[] decryptedMessage, NexoDeri
}
}

/**
* Generates a random IV nonce using a secure random number generator.
*/
private byte[] generateRandomIvNonce() {
byte[] ivNonce = new byte[NEXO_IV_LENGTH];
SecureRandom secureRandom;
try {
secureRandom = SecureRandom.getInstance("NativePRNGNonBlocking");
} catch (NoSuchAlgorithmException e) {
// Fallback to default SecureRandom implementation
secureRandom = new SecureRandom();
}
secureRandom.nextBytes(ivNonce);
Expand Down

0 comments on commit 5af45ad

Please sign in to comment.