I'm writing an apex class to receive incoming AES256 encrypted data. I have a private key safely stored, and the incoming data includes an iv. My logs show me that the retrieved private key is 32 bytes, and the iv is 16 bytes.
22:39:11.4 (12736525)|VARIABLE_ASSIGNMENT|[20]|iv|BLOB(16 bytes)
22:39:11.4 (42204647)|VARIABLE_ASSIGNMENT|[35]|key|BLOB(32 bytes)
My cipherText is 426 bytes. Is that required to be a multiple of 16?
22:39:11.4 (42415528)|VARIABLE_ASSIGNMENT|[39]|cipherText|BLOB(426 bytes)
The line that is throwing the error is this:
Blob decryptedBlob = Crypto.decrypt('AES256', key, iv, cipherText);
22:39:11.4 (42576983)|METHOD_ENTRY|[40]||System.Crypto.decrypt(String, Blob, Blob, Blob)
I'm getting this error: Input length must be multiple of 16 when decrypting with padded. I'm unclear which input must be a multiple of 16. Is it the cipherText? The cipherText I'm using is generated with the Crypto.encrypt() method.
_________________________ EDIT on Day 2 ____________
Thanks @identigral for the links. Good to have the background understanding. My takeaway is that YES the input cipherText does need to be a multiple of 16 bytes.
For reference, here's the doc on the encryption they're using.
The roundtrip encrypt/decrypt works fine, but when I try to test it with a Postman call to the webhook endpoint, that's where I continue to get the error.
I'm taking my JSON data, encrypting it, then I was using EncodingUtil.base64Encode() to encode the encrypted blob, and then sending that string in the httpPost. That gives me the error. However, if I use EncodingUtil.convertToHex() and update my Apex to convertFromHex() it works.
//this is how I'm generating values to test in the httpPost
Blob exampleIv = Blob.valueOf('Example of IV123');
String encodedIv = EncodingUtil.base64Encode(exampleIv);
System.debug(' encodedIv: ' + encodedIv);
List<SecurePay_Key__mdt> keys = [SELECT Private_Key__c FROM SecurePay_Key__mdt WHERE Label = 'Testing'];
String secretKeyString = keys[0].Private_Key__c;
System.debug(' Found key in custom settings: ' + secretKeyString);
Blob key = EncodingUtil.base64Decode(secretKeyString);
Blob data = Blob.valueOf('{"order_id":6,"fields":{"receive_date":"2021-06-29 15:30:45","amount":2.79,"status":"Pending","first_name":"Lisa","last_name":"Simpson","email":"lisa@simpsons.com","street_address":"23 Main St","supplemental_address_1":"Mosley Corner","city":"Springfield","state":"CA","postal_code":90210,"prefix":"Mr.","suffix":"I","affiliation":"Crusty\'s Clown College","uni":"uni y","heard_from":"Milhouse","is_test":1}}');
Blob encrypted = Crypto.encrypt('AES256', key, exampleIv, data);
String hexString = EncodingUtil.convertToHex(encrypted);
System.debug(' hexString: ' + hexString);
String encodedString = EncodingUtil.base64Encode(encrypted);
System.debug(' encodedString: ' + encodedString);
I just tried this on a whim because the error message said 'multiple of 16' and hexidecimal... The problem is I don't have control of the incoming httpPost, and they're just using base64Encoding.
Here's my Apex on the receiving end of the webhook:
global without sharing class InboundProcessor {
//create a method to receive HTTP Post
@HttpPost
global static void handleInboundWebhook() {
System.debug(' Inbound Webhook');
Boolean isTest = false;
Blob iv = null;
//create rest response
RestResponse res = RestContext.response;
//map the parameters
Map<String, String> params = RestContext.request.params;
//check for the encoded iv in the params
if(params.containsKey('iv')){
System.debug(' Found iv in params:' + params.get('iv'));
//decode the iv
iv = EncodingUtil.base64Decode(params.get('iv'));
}
if(params.containsKey('isTest')){
isTest = params.get('isTest') == 'true' ? true : false;
if(isTest){
System.debug(' Test mode');
}
}
//get the private key
String label = isTest ? 'Testing' : 'Production';
List<SecurePay_Key__mdt> keys = [SELECT Private_Key__c FROM SecurePay_Key__mdt WHERE Label = :label];
if(keys.size() > 0){
String secretKeyString = keys[0].Private_Key__c;
System.debug(' Found key in custom settings: ' + secretKeyString);
Blob key = EncodingUtil.base64Decode(secretKeyString);
try{
//decode and decrypt the data using AES256
String encryptedString = params.get('fields');
Blob cipherText = EncodingUtil.base64Decode(encryptedString);
// Blob cipherText = EncodingUtil.convertFromHex(encryptedString);
Blob decryptedBlob = Crypto.decrypt('AES256', key, iv, cipherText);
System.debug(' Decrypted blob: ' + decryptedBlob.toString());
String decodedString = decryptedBlob.toString();
System.debug(' Decrypted string: ' + decodedString);
//fire the platform event
fireWebhookEvent(decodedString);
// Set the response values for Web Service
RestContext.response.statusCode = 200;
RestContext.response.addHeader('Content-Type', 'text/html;charset=utf-8');
} catch(Exception e){
System.debug('❌ Error: ' + e.getMessage());
System.debug('❌ Error: ' + e.getStackTraceString());
res.statusCode = 500;
res.responseBody = Blob.valueOf(e.getMessage());
}
}else{
System.debug('❌ No key found');
res.statusCode = 500;
res.responseBody = Blob.valueOf('No key found');
}
}