import java.security.Key;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import com.nimbusds.jose.JWEHeader;
public class JweCreatorTest {
// 96. 120. import 128 are supported
private static final int AUTHENTICATION_TAG_LENGTH = 96;
// key length in bits
private final static int KEY_LENGTH = 256;
// iteration count
private final static int ITERATION_COUNT = 1000;
private final static String KDF_ALGO = "PBKDF2WithHmacSHA512";
private final static String KEY_WRAP_ALGO = "AESWrap_256";
private final static String AES_ALGO = "AES";
private static final String ENC_KEY = "ddsdsdfffff";
public static void main(String[] args) throws Exception {
// create JWE Compact using sample PAN
String jweCompact = cipher("4444111122223333");
System.out.println("Encrypted value is;" + jweCompact);
// now decipher the content from JwE Compart
// String plainText = decipher(jweCompact);
// System.out.println("Decrypted value is: " + plainText);
}
private static String cipher (String plainText) throws Exception {
//generate a salt value and this must be unique for each request
final byte[] salt = new byte[32];
new SecureRandom().nextBytes(salt);
// construct the JOSE Header
String jweHeader = "{\"p2s\": \"" + Base64.getUrlEncoder().encodeToString(salt)
+ "\", \"p2c\": 1000, \"enc\": \"A256GCM\", \"alg\": \"PBES2-H5512+4256KW\"}";
// define additional authentication data to be the Base64URL encoded JWE (JOSE) Header
byte[] aadBytes = Base64.getUrlEncoder().encode (jweHeader.getBytes());
// generate iv (the AES/GCM nonce)
final byte[] initializationVector = new byte[12];
new SecureRandom().nextBytes(initializationVector);
// generate the content encryption key
KeyGenerator keyGen = KeyGenerator.getInstance(AES_ALGO);
keyGen. init(KEY_LENGTH /* key-len in bits */, new SecureRandom ()) ;
SecretKey contentEncryptionKey = keyGen.generateKey() ;
// encrypt the plain-text sensitive data using the Content Encryption Key
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init (Cipher.ENCRYPT_MODE, contentEncryptionKey, new GCMParameterSpec (AUTHENTICATION_TAG_LENGTH,initializationVector));
cipher.updateAAD(aadBytes);
final byte[] tmp = cipher.doFinal(plainText.getBytes());
// The cipher-text bytes include the ciphr-text AND the authentication tag.
// Need to separate these values
final byte[] authenticationTag = Arrays.copyOfRange(tmp, tmp.length - (AUTHENTICATION_TAG_LENGTH / 8),
tmp. length);
final byte[] cipherTextBytes = Arrays.copyOfRange(tmp, 0, tmp.length - (AUTHENTICATION_TAG_LENGTH / 8));
// Create the PBEKeySpec with the given password, salt, and iteration
PBEKeySpec pbeKeySpec = new PBEKeySpec(ENC_KEY.toCharArray(), salt, ITERATION_COUNT, KEY_LENGTH);
// derive a Key-Encrypting Key (KEK) based on password and other PBEKeySpec attributes
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(KDF_ALGO);
SecretKey derivedKEK = keyFactory.generateSecret(pbeKeySpec);
// wrap the CEK using derived KEK
Cipher contentEncKeyCipher = Cipher.getInstance(KEY_WRAP_ALGO);
contentEncKeyCipher.init(Cipher.WRAP_MODE, new SecretKeySpec(derivedKEK.getEncoded(),AES_ALGO));
byte[] encryptedContentEncryptionkey = contentEncKeyCipher.wrap(contentEncryptionKey);
// create compact serialization of JWE
String jweCompact = Base64.getEncoder().encodeToString(jweHeader.getBytes())+ "."
+ Base64.getEncoder().encodeToString(encryptedContentEncryptionkey) + "."
+ Base64.getEncoder().encodeToString(initializationVector) + "."
+ Base64.getEncoder().encodeToString(cipherTextBytes) + "."
+ Base64.getEncoder().encodeToString(authenticationTag);
return jweCompact;
}
private static String decipher (String jweCompact) throws Exception {
if (jweCompact == null)
throw new RuntimeException();
String[] parts = jweCompact.split("([.])");
if (parts.length != 5) {
throw new Exception("invalid input");
}
byte[] hdrBytes = Base64.getUrlDecoder().decode(parts[0]); // headerBytes
byte[] ekBytes = Base64.getUrlDecoder().decode(parts[1]); // encryptedcontentEncryptionkey
byte[] ivBytes = Base64.getUrlDecoder().decode(parts[2]); // initializationVector
byte[] ctBytes = Base64.getUrlDecoder().decode(parts[3]); // cipherTextBytes
byte[] atBytes = Base64.getUrlDecoder().decode(parts[4]); // authenticationTag
// get salt value, and iteration count from header
JWEHeader header = new JWEHeader(JWEHeader.parse(new String (hdrBytes)));
final byte[] salt = header.getPBES2Salt().decode();
final int iterationcount = header.getPBES2Count();
// Create the PBEKeySpec with the given password, salt, and iteration
PBEKeySpec pbeKeySpec= new PBEKeySpec (ENC_KEY.toCharArray(), salt, iterationcount, KEY_LENGTH);
//derive a key based on password and other PBEKeySpec attributes
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(KDF_ALGO);
SecretKey derivedKEK = keyFactory.generateSecret(pbeKeySpec);
// un-wrap the CEK using derived KEK
Cipher contentDecKeyCipher = Cipher.getInstance(KEY_WRAP_ALGO);
contentDecKeyCipher.init(Cipher.UNWRAP_MODE,new SecretKeySpec(derivedKEK.getEncoded(), AES_ALGO));
Key k = contentDecKeyCipher.unwrap(ekBytes, AES_ALGO, Cipher.SECRET_KEY);
SecretKeySpec contentDecryptionKey = new SecretKeySpec(k.getEncoded(), AES_ALGO);
//decrypt the content
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init (Cipher.DECRYPT_MODE, contentDecryptionKey,new GCMParameterSpec(AUTHENTICATION_TAG_LENGTH, ivBytes));
byte[] aadBytes = Base64.getUrlEncoder().encode(hdrBytes);
cipher.updateAAD(aadBytes);
// need to append the authentication tag to the ciphertext
byte[] cipherTextAndAT = new byte[ ctBytes.length + atBytes.length];
System.arraycopy(ctBytes, 0, cipherTextAndAT,0, ctBytes.length);
System.arraycopy(ctBytes, 0, cipherTextAndAT, ctBytes.length,atBytes.length);
String plaintext = new String( cipher.doFinal( cipherTextAndAT));
return plaintext;
}
}
As part of POC .This is my java code to cipher certain fields in my json request payload.
1. Could you please help me to get the js code for the same. I tried to use the below js code but it is throwing error: unsupported key type .
2. After getting cipher value in both java and js will someone get the same result if he use java code to decipher ?
const jose = require('node-jose');
const crypto = require('crypto');
// 96, 120, or 128 are supported
const AUTHENTICATION_TAG_LENGTH = 96;
// key length in bits
const KEY_LENGTH = 256;
// iteration count
const ITERATION_COUNT = 1000;
const KDF_ALGO = 'PBKDF2WithHmacSHA512';
const KEY_WRAP_ALGO = 'A256KW';
const AES_ALGO = 'A256GCM';
const ENC_KEY = 'ddsdsdfffff';
async function createJwe(plainText) {
// generate a salt value and this must be unique for each request
const salt = crypto.randomBytes(32);
// construct the JOSE Header
const jweHeader = {
p2s: salt.toString('base64url'),
p2c: ITERATION_COUNT,
enc: AES_ALGO,
alg: 'PBES2-HS512+A256KW',
};
// define additional authentication data to be the Base64URL encoded JWE (JOSE) Header
const aadBytes = Buffer.from(JSON.stringify(jweHeader)).toString('base64url');
// generate iv (the AES/GCM nonce)
const initializationVector = crypto.randomBytes(12);
// generate the content encryption key
const contentEncryptionKey = await jose.JWK.createKey(KEY_LENGTH, AES_ALGO);
// encrypt the plain-text sensitive data using the Content Encryption Key
const cipher = await jose.JWE.createEncrypt(
{ format: 'compact', contentAlg: AES_ALGO, fields: { iat: Math.floor(Date.now() / 1000) } },
contentEncryptionKey,
);
cipher.update(aadBytes, 'utf8');
const encrypted = await cipher.final(Buffer.from(plainText, 'utf8'));
// The cipher-text bytes include the ciphr-text AND the authentication tag.
// Need to separate these values
const authenticationTag = encrypted.slice(-AUTHENTICATION_TAG_LENGTH / 8);
const cipherTextBytes = encrypted.slice(0, -AUTHENTICATION_TAG_LENGTH / 8);
// Create the PBEKeySpec with the given password, salt, and iteration
const pbeKeySpec = {
key: Buffer.from(ENC_KEY),
salt,
iterations: ITERATION_COUNT,
hash: 'sha512',
};
// derive a Key-Encrypting Key (KEK) based on password and other PBEKeySpec attributes
const derivedKEK = await jose.JWK.createKey('PBKDF2', pbeKeySpec, { alg: 'HS512' });
// wrap the CEK using derived KEK
const encryptedContentEncryptionkey = await jose.JWE.encrypt(contentEncryptionKey, derivedKEK, { format: 'compact', fields: { iat: Math.floor(Date.now() / 1000) } });
// create compact serialization of JWE
const jweCompact = `${aadBytes}.${
encryptedContentEncryptionkey.match(/.{1,64}/g).join('\n')
}.${
initializationVector.toString('base64url')
}.${
cipherTextBytes.toString('base64url')
}.${
authenticationTag.toString('base64url')
}`;
return jweCompact;
}
async function main() {
const plainText = '4444111122223333';
const jweCompact = await createJwe(plainText);
console.log('Encrypted value is:', jweCompact);
}
main().catch(err => console.error(err));