I am busy in a decryption of encrypted data work.
Another business provider has provided me with a Private_key.pem file, and then I should use it to decrypt the encrypted data they provided.
They have provided a demo coding in Ruby to help me do this. It works well in Ruby, but when I want to make it in Java, I find that there is a lot of difficulties.
The demo in Ruby:
def decrypt(key, encrypted_data)
# Decode the Base64 encoded `encrypted_message` and `tag`
ciphertext = Base64.strict_decode64(encrypted_data[:encrypted_message])
mac = Base64.strict_decode64(encrypted_data[:tag])
# Compute shared secret using the private key of the certificate and `ephemeral_public_key`
ephemeral_public_key_pem = encrypted_data[:ephemeral_public_key]
ephemeral_public_key = OpenSSL::PKey::EC.new(ephemeral_public_key_pem).public_key
shared_secret = key.dh_compute_key(ephemeral_public_key)
# Compute the hmac of decoded `encrypted_message` and ensure it matches the decoded value of `tag`
cipher = OpenSSL::Cipher.new('AES-256-CTR')
mac_digest = OpenSSL::Digest.new('SHA256')
mac_length = mac_digest.digest_length / 2
key_pair = OpenSSL::KDF.hkdf(
shared_secret,
length: cipher.key_len + mac_length,
hash: 'SHA256',
salt: '',
info: ''
)
cipher_key = key_pair.byteslice(0, cipher.key_len)
hmac_key = key_pair.byteslice(-mac_length, mac_length)
computed_mac = OpenSSL::HMAC.digest(mac_digest, hmac_key, ciphertext).byteslice(0, mac_length)
raise OpenSSL::PKey::ECError, "Invalid Message Authenticaton Code" unless OpenSSL.secure_compare(computed_mac, mac)
# Decrypt `encrypted_message`
cipher.decrypt
cipher.iv = ("\x00" * 16).force_encoding(Encoding::BINARY)
cipher.key = cipher_key
cipher.update(ciphertext) + cipher.final
end
# the encrypted data to be decrypted
encrypted_data = {
encrypted_message: "7oeGaIk3Tja5fkyB3P[...]urLyxgW4J9lu2x769+",
ephemeral_public_key: "-----BEGIN PUBLIC KEY-----\nMEB3wYo[...]HKFkwjQ==\n-----END PUBLIC KEY-----\n",
tag: "LDCZ1tbHX8t+KPO9w=="
}
# Use `fingerprint` to identify the certificate that's used for encryption
key = OpenSSL::PKey::EC.new(File.open('path/to/private-key.file').read)
# decrypt
decrypt(key, encrypted_data)
One of the difficulties is I can't read the private_key.pem properly. The code below:
public static KeyFactory keyFactory;
static {
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.addProvider(new BouncyCastleProvider());
}
try {
keyFactory = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
} catch (NoSuchAlgorithmException | NoSuchProviderException e) {
}
}
public static PrivateKey getPrivateKey(String filePath) throws Exception {
String privateKeyPEM = new String(Files.readAllBytes(Paths.get(filePath)), StandardCharsets.UTF_8);
String privateKeyPEMTrimmed = privateKeyPEM
.replace("-----BEGIN EC PRIVATE KEY-----", "")
.replace("-----END EC PRIVATE KEY-----", "")
.replaceAll("\n", "");
byte[] privateKeyBytes = org.apache.commons.codec.binary.Base64.decodeBase64(privateKeyPEMTrimmed)
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
return keyFactory.generatePrivate(pkcs8EncodedKeySpec);
}
The private_key.pem file looks like:
-----BEGIN EC PRIVATE KEY-----
MHADASDAS265546XCADAP8zAOJPkW7OXTqotoAoGCCqGSM49
AadADASDASDCLKJOI45646asdcasDASDASD122165165D4sdasdsadqDASFAGFAADy
Aufttttttttttttttttttttw==
-----END EC PRIVATE KEY-----
When I run this code, it returns an error:
java.security.spec.InvalidKeySpecException: encoded key spec not recognized: unknown object in getInstance: org.bouncycastle.asn1.DEROctetString
at org.bouncycastle.jcajce.provider.asymmetric.util.BaseKeyFactorySpi.engineGeneratePrivate(Unknown Source)
at org.bouncycastle.jcajce.provider.asymmetric.ec.KeyFactorySpi.engineGeneratePrivate(Unknown Source)
I have searched methods and one on them is changing the private_key.file to PKCS#8 format format in OpenSSL, I do so and it works without error in this code. However, the result is not equal to tag of the encrypted_data in the decrypt function.
The decrypt function is:
public static byte[] decrypt(Key key, ShopifyCreditCardMethodData encryptedPayload) throws Exception {
// Decode the Base64 encoded `encrypted_message` and `tag`
byte[] ciphertext = Base64.getDecoder().decode(encryptedPayload.getEncrypted_message());
byte[] mac = Base64.getDecoder().decode(encryptedPayload.getTag());
// Compute shared secret using the private key of the certificate and `ephemeral_public_key`
String s = encryptedPayload.getEphemeral_public_key().replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\n", "");
byte[] ephemeralPublicKeyBytes = Base64.getDecoder().decode(s);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(ephemeralPublicKeyBytes);
PublicKey ephemeralPublicKey = keyFactory.generatePublic(keySpec);
KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH", BouncyCastleProvider.PROVIDER_NAME);
keyAgreement.init((PrivateKey) key);
keyAgreement.doPhase(ephemeralPublicKey, true);
byte[] sharedSecret = keyAgreement.generateSecret();
// Compute the hmac of decoded `encrypted_message` and ensure it matches the decoded value of `tag`
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding", BouncyCastleProvider.PROVIDER_NAME);
MessageDigest macDigest = MessageDigest.getInstance("SHA-256", BouncyCastleProvider.PROVIDER_NAME);
int macLength = macDigest.getDigestLength() / 2;
byte[] keyPair = hkdf(sharedSecret, cipher.getBlockSize() + macLength, "HmacSHA256");
byte[] cipherKey = Arrays.copyOfRange(keyPair, 0, cipher.getBlockSize());
byte[] hmacKey = Arrays.copyOfRange(keyPair, keyPair.length - macLength, keyPair.length);
Mac hmac = Mac.getInstance("HmacSHA256");
hmac.init(new SecretKeySpec(hmacKey, "HmacSHA256"));
byte[] computedMac = Arrays.copyOf(hmac.doFinal(ciphertext), macLength);
if (!MessageDigest.isEqual(computedMac, mac)) {
throw new Exception("Invalid Message Authenticaton Code");
}
// Decrypt `encrypted_message`
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(cipherKey, "AES"), new IvParameterSpec(new byte[cipher.getBlockSize()]));
return cipher.doFinal(ciphertext);
}
public static byte[] hkdf(byte[] ikm, int length, String hashAlgorithm, byte[] salt, byte[] info) throws Exception {
Mac hmac = Mac.getInstance(hashAlgorithm);
hmac.init(new SecretKeySpec(salt != null ? salt : new byte[hmac.getMacLength()], hashAlgorithm));
byte[] prk = hmac.doFinal(ikm);
byte[] t = new byte[0];
byte[] okm = new byte[length];
for (int i = 0; i < Math.ceil((double) length / hmac.getMacLength()); i++) {
hmac.init(new SecretKeySpec(prk, hashAlgorithm));
hmac.update(t);
hmac.update(info);
hmac.update((byte) (i + 1));
t = hmac.doFinal();
System.arraycopy(t, 0, okm, i * hmac.getMacLength(), Math.min(hmac.getMacLength(), length - i * hmac.getMacLength()));
}
return okm;
}
public static byte[] hkdf(byte[] ikm, int length, String hashAlgorithm) throws Exception {
return hkdf(ikm, length, hashAlgorithm, null, new byte[0]);
}
I am not familiar with the asymmetric encryption system, and I try me best, but I can't find questions in my Java code with the Ruby code.