ECDH + JWE encryption/decryption using jose4j - java

I am trying to implement ECDH encryption/decryption along with JWE in Android (Java).
I have found the jose4j and Nimbus JOSE libraries that aim to do everything I need but appears that it's more challenging than I thought.
If anybody is familiar then it's for 3D Secure 2.0...
In the spec below:
SDK = the local side
DS = Directory Server (the other side)
Next is the spec:
Given: P(DS) - an EC public key (provided in PEM format, can be transformed to PublicKey or to JWK)
Generate a fresh ephemeral key pair (Q(SDK), d(SDK))
Conduct a Diffie-Hellman key exchange process according to JWA (RFC7518) in Direct Key Agreement mode using curve P-256, d(SDK) and P(DS) to produce a CEK. The parameter values supported in this version of the specification are:
"alg":ECDH-ES
"apv":DirectoryServerID
"epk":P(DS),inJSONWebKey(JWK)format {"kty":"EC", "crv":"P-256"}
All other parameters: not present
CEK:"kty":oct-256bits
Generate 128-bit random data as IV
Encrypt the JSON object according to JWE (RFC7516) using the CEK and JWE Compact Serialization. The parameter values supported in this version of the specification are:
"alg":dir
"epk":Q(SDK) as {"kty": "EC", "crv": "P-256"}
"enc":either"A128CBC-HS256"or"A128GCM"
All other parameters: not present
If the algorithm is A128CBC-HS256 use the full CEK or if the algorithm is A128GCM use the leftmost 128 bits of the CEK.
Delete the ephemeral key pair (Q(SDK),d(SDK))
Makes the resulting JWE available to the 3DS Server as SDK Encrypted Data
If someone has implemented this exact spec and can share the code this would be brilliant!!
There's an example of creating JWT using ECDH in the examples of jose4j:
https://bitbucket.org/b_c/jose4j/wiki/JWT%20Examples (the last example, titled as "Producing and consuming a nested (signed and encrypted) JWT").
But this example is not exactly what I need. It creates a token while I need to encrypt a text.
Starting from "CEK:"kty":oct-256bits" in the spec above I don't understand what to do.
Here's my code (so far) using Nimbus lib:
public String nimbus_encrypt(String plainJson, ECPublicKey otherPublicKey, String directoryServerId) throws JOSEException {
JWEHeader jweHeader = new JWEHeader(
JWEAlgorithm.ECDH_ES,
EncryptionMethod.A128CBC_HS256,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
Base64URL.encode(directoryServerId),
null,
0,
null,
null,
null,
null);
JWEObject jwe = new JWEObject(jweHeader, new Payload(plainJson));
jwe.encrypt(new ECDHEncrypter(otherPublicKey));
String serializedJwe = jwe.serialize();
Log.d("[ENCRYPTION]", "nimbus_encrypt: jwe = " + jwe.getHeader());
Log.d("[ENCRYPTION]", "nimbus_encrypt: serializedJwe = " + serializedJwe);
return serializedJwe;
}
This is the nimbus output:
nimbus_encrypt: jwe = {"epk":{"kty":"EC","crv":"P-256","x":"AS0GRfAOWIDONXxaPR_4IuNHcDIUJPHbACjG5L7x-nQ","y":"xonFn1vRASKUTdCkFTwsl16LRmSe-bAF8EO4-mh1NYw"},"apv":"RjAwMDAwMDAwMQ","enc":"A128CBC-HS256","alg":"ECDH-ES"}
nimbus_encrypt: serializedJwe = eyJlcGsiOnsia3R5IjoiRUMiLCJjcnYiOiJQLTI1NiIsIngiOiJBUzBHUmZBT1dJRE9OWHhhUFJfNEl1TkhjRElVSlBIYkFDakc1TDd4LW5RIiwieSI6InhvbkZuMXZSQVNLVVRkQ2tGVHdzbDE2TFJtU2UtYkFGOEVPNC1taDFOWXcifSwiYXB2IjoiUmpBd01EQXdNREF3TVEiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiRUNESC1FUyJ9..Pi48b7uj3UilvVXKewFacg.0sx9OkHxxtZvkVm-IENRFw.bu5GvOAwcZxdxaDKWIBqwA
Here's my code (so far, using #Brian-Campbell's answer) using jose4j lib:
public String jose4j_encrypt(String plainJson, PublicKey otherPublicKey, String directoryServerId) throws JoseException {
JsonWebEncryption jwe = new JsonWebEncryption();
jwe.setAlgorithmHeaderValue(KeyManagementAlgorithmIdentifiers.ECDH_ES);
jwe.setEncryptionMethodHeaderParameter(ContentEncryptionAlgorithmIdentifiers.AES_128_CBC_HMAC_SHA_256);
jwe.setHeader(HeaderParameterNames.AGREEMENT_PARTY_V_INFO, Base64Url.encodeUtf8ByteRepresentation(directoryServerId));
jwe.setKey(otherPublicKey);
jwe.setPayload(plainJson);
String serializedJwe = jwe.getCompactSerialization();
Log.d("[ENCRYPTION]", "jose4j_encrypt: jwe = " + jwe);
Log.d("[ENCRYPTION]", "jose4j_encrypt: serializedJwe = " + serializedJwe);
return serializedJwe;
}
This is the jose4j output:
jose4j_encrypt: jwe = JsonWebEncryption{"alg":"ECDH-ES","enc":"A128CBC-HS256","apv":"RjAwMDAwMDAwMQ","epk":{"kty":"EC","x":"prvyhexJXDWvPQmPA1xBjY8mkHEbrEiJ4Dr-7_5YfdQ","y":"fPjw8UdfzgkVTppPSN5o_wprItKLwecoia9yrWi38yo","crv":"P-256"}}
jose4j_encrypt: serializedJwe = eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTEyOENCQy1IUzI1NiIsImFwdiI6IlJqQXdNREF3TURBd01RIiwiZXBrIjp7Imt0eSI6IkVDIiwieCI6InBydnloZXhKWERXdlBRbVBBMXhCalk4bWtIRWJyRWlKNERyLTdfNVlmZFEiLCJ5IjoiZlBqdzhVZGZ6Z2tWVHBwUFNONW9fd3BySXRLTHdlY29pYTl5cldpMzh5byIsImNydiI6IlAtMjU2In19..gxWYwFQSOqLk5HAgs7acdA.mUIHBiWpWSlQaEOJ_EZGYA.eiTe-88fw-Jfuhji_W0rtg
As can be seen the "alg" header in the final result is "ECDH-ES" and not "dir" as required.
If I would implement both sides of the communication then it would be enough, but with this spec seems like many configurations are missing here...
Code using jose4j is longer and seems more configurable but I couldn't construct something valuable enough to post here.
The main missing part for me is how to generate the CEK from the spec above.
Thank you.
EDIT
Added jose4j code above and added the outputs...

Below is some example code using jose4j that I think does what you're looking for. The example you pointed to is similar with the plaintext of the JWE being a JWS/JWT but it can be any arbitrary content. The details of the CEK generation/derivation are taken care of by the underlying JWE functionality. Please note that this only encrypts the content and doesn't provide integrity protection or sender authentication.
String encodedCert = "MIIBRjCB7KADAgECAgYBaqxRCjswDAYIKoZIzj0EAwIFADApMQswCQYDVQQGEwJDQTEMMAoGA1UE\n" +
"ChMDbWVoMQwwCgYDVQQDEwNtZWgwHhcNMTkwNTEyMTM1MjMzWhcNMjAwNTExMTM1MjMzWjApMQsw\n" +
"CQYDVQQGEwJDQTEMMAoGA1UEChMDbWVoMQwwCgYDVQQDEwNtZWgwWTATBgcqhkjOPQIBBggqhkjO\n" +
"PQMBBwNCAAQH83AhYHCehKj7M5+UTNshwLFqqqJWGrJPNj9Kr7xvxtcZnyjq+AKLGMLfdk/G7yb8\n" +
"4vIh0cJwtVs70WgIXT8xMAwGCCqGSM49BAMCBQADRwAwRAIgO0PJRzan2msHpcvcqhybzeualDea\n" +
"/X2QGAWCYT+sNiwCIDMrfhrzUQ6uIX4vnB8AYqb85Ssl7Qcl9nYtjHb08NR8";
X509Util x509Util = new X509Util();
X509Certificate x509Certificate = x509Util.fromBase64Der(encodedCert);
// the JWE object
JsonWebEncryption jwe = new JsonWebEncryption();
// The output of the ECDH-ES key agreement will be used as the content encryption key
jwe.setAlgorithmHeaderValue(KeyManagementAlgorithmIdentifiers.ECDH_ES);
// The content encryption key is used to encrypt the payload
// with a composite AES-CBC / HMAC SHA2 encryption algorithm
jwe.setEncryptionMethodHeaderParameter(ContentEncryptionAlgorithmIdentifiers.AES_128_CBC_HMAC_SHA_256);
// don't think you really need this but you had ""apv":DirectoryServerID" in the question so...
jwe.setHeader(HeaderParameterNames.AGREEMENT_PARTY_V_INFO, Base64Url.encodeUtf8ByteRepresentation("<<DirectoryServerID>>"));
// We encrypt to the receiver using their public key
jwe.setKey(x509Certificate.getPublicKey());
// and maybe put x5t to help the receiver know which key to use in decryption
jwe.setX509CertSha1ThumbprintHeaderValue(x509Certificate);
// What is going to be encrypted
jwe.setPayload("Your text here. It can be JSON or whatever.");
// Produce the JWE compact serialization, which is a string consisting of five dot ('.') separated
// base64url-encoded parts in the form Header..IV.Ciphertext.AuthenticationTag
String serializedJwe = jwe.getCompactSerialization();
System.out.println(serializedJwe);

Related

MongoDB Java - How does the CMK work?/Different keys producing same result

MongoDB offers a feature called Client-Side Field Level Encryption (CSFLE) which allows applications to encrypt data before storing it in the database.
As the name suggests you can control the encryption for each field of a document individually using a different Data Encryption Key (DEK) for every field if needed.
These DEKs are stored in a collection called Key Vault where the actual key material is encrypted with a key named Customer Master Key (CMK).
The CMK has a fixed size of 96 bytes.
To read an encrypted field from a document, an application has to
grab the document from the database
fetch the corresponding DEK from the Key Vault
decrypt the DEK using the CMK
decrypt the encrypted field from the document using the decrypted DEK
Storing an encrypted field uses the same mechanism.
Key element is the CMK, especially regarding security as MongoDB points out.
My understanding of encryption so far is that larger keys usually provide more security and that you have to use the same key to decrypt something you've encrypted.
However, while working on a Java-project using CSFLE I noticed MongoDB behaving differently from what I'd expect: I can use different CMKs and still get a valid decryption.
To be more precise, any change in the last 32 bytes of the original CMK does not throw an exception.
How is this possible? Is this intended?
I don't understand this so I'm looking forward to any kind of explanation.
How to replicate
You can copy the example code from MongoDB to replicate this.
I've made some adjustments to the code (e.g. authentication)
MongoClientSettings clientSettings = MongoClientSettings.builder()
.uuidRepresentation(UuidRepresentation.STANDARD)
.applyToClusterSettings(builder -> builder.hosts(Arrays.asList(new ServerAddress("localhost", 27017))))
.credential(...).build();
ClientEncryptionSettings clientEncryptionSettings = ClientEncryptionSettings.builder()
.keyVaultMongoClientSettings(MongoClientSettings.builder()
.applyToClusterSettings(builder -> builder.hosts(Arrays.asList(new ServerAddress("localhost", 27017))))
.credential(...).build())
.keyVaultNamespace(keyVaultNamespace.getFullName()).kmsProviders(kmsProviders).build();
to keep the DEK over subsequent runs
BsonBinary dataKeyId = null;
List<String> keyAltNames = List.of("testCMK");
MongoCursor<Document> cursor = keyVaultCollection.find().iterator();
try {
while (cursor.hasNext() && dataKeyId == null) {
Document key = cursor.next();
List<String> altNames = key.getList("keyAltNames", String.class);
if (altNames != null) {
for (String name : altNames) {
if (name.contentEquals("testCMK")) {
dataKeyId = new BsonBinary(key.get("_id", UUID.class));
break;
}
}
}
}
if (dataKeyId == null)
dataKeyId = clientEncryption.createDataKey("local", new DataKeyOptions().keyAltNames(keyAltNames));
} finally {
cursor.close();
}
and only insert the document once
if (collection.find().first() == null)
collection.insertOne(new Document("encryptedField", encryptedFieldValue));
Document doc = collection.find().first();
// Explicitly decrypt the field
BsonValue decrypted = clientEncryption.decrypt(
new BsonBinary(BsonBinarySubType.ENCRYPTED, doc.get("encryptedField", Binary.class).getData()));
System.out.println("decrypted: " + decrypted);
The original example code doesn't set the BsonBinarySubType which causes clientEncryption.decrypt() to do nothing because the passed BsonBinary is of type 0 (generic binary) instead of 6 (encrypted).
Generating the master key
The CMK is always generated as follows
final byte[] localMasterKey = new byte[96];
for (int i = 0; i < localMasterKey.length; i++)
localMasterKey[i] = (byte) (i + 1);
Running the program multiple times will always produce the same output:
raw value: BsonString{value='123456789'}
decrypted: BsonString{value='123456789'}
Now change the CMK by adding localMasterKey[0] = 0; after the for-loop which generates the key.
Running the program will result in an java.lang.reflect.InvocationTargetException caused by com.mongodb.crypt.capi.MongoCryptException: HMAC validation failure which is expected because the DEK cannot be decrypted with a different CMK.
However, if we instead add localMasterKey[localMasterKey.length - 1] = 0;, the program will again produce
raw value: BsonString{value='123456789'}
decrypted: BsonString{value='123456789'}
I would've expected an exception to be thrown but it decrypts the field with the correct value.
To summarize, the encryption/decryption fails if the generated master key differs from the original (first) master key in any of the first 64 bytes but succeeds when the difference is in the last 32 bytes.
This is not limited to a one byte difference - decryption works even when all 32 bytes are different.
Why is that?

how to make public key from string on android for JWE?

I have a public key (RSA) as a string. I want to use this key to create a secret code, lets say the unencrypted secret code is "TEST TEST" without qoutes.
How can this be achieved? I mean I tried the following but stuck on creating the Key object from the public key string
I used nimbus-jose-jwt library. But wasn't able to create RSA public key object in the following code
// Create the header
val header = JWEHeader(JWEAlgorithm.RSA1_5, EncryptionMethod.A256CBC_HS512)
// Set the plain text
val payload = Payload(decryptedText)
// Create the JWE object and encrypt it
val jweObject = JWEObject(header, payload)
jweObject.encrypt(JWEEncrypter)
// Serialise to compact JOSE form...
val actual = jweObject.serialize()
Also I used another library named org.bitbucket.b_c:jose4j but same thing, I wasn't able to successfully create public key object from the public key string that I got.
Here is the code snippet that I used but failed to achieve what I want and be able to correctly encrypt TEST TEST.
val jwe = JsonWebEncryption()
jwe.payload = decryptedText
jwe.algorithmHeaderValue = KeyManagementAlgorithmIdentifiers.RSA1_5
jwe.encryptionMethodHeaderParameter = ContentEncryptionAlgorithmIdentifiers.AES_256_CBC_HMAC_SHA_512
jwe.key = stringToRSAPublicKey(publicKey)
val serializedJwe = jwe.compactSerialization
The public key string is the following
"MIIBITANBgkqhkiG9w0BAQEFAAOCAQ4AMIIBCQKCAQBGvNkLnetAtR+QSttxIkQ9" +
"mH7pbbjl2UqRu5UDO9kuEiYh4b70JxPN8v1exkuW/FLmxKjdRVq7gNWstumIGm1W" +
"8cf4RtFj88pvZUaVg6NZ21iLAIHtnhb2D/4eBOI8HXdhdZ+bEd+BJbu1rlqm0Rs1" +
"1jzYukR35/n44me3fbP9DH3JmSM8s0F8RmlIY0VqDnSOCOazNupVtJQFWeDIyfcV" +
"/coW+RRrFq5KNwnHPxdl5o3PR3OZgV27H/eBuKxIEGvjBUYchSjAAdJYAnfISvcd" +
"huLeYocZGi5WHEswrQBoUG8GflcdMJTvtTL5PtJG2WdcurIQA6iD2fSdBgQpARJF" +
"AgMBAAE=
In a nutshell, I need to convert this taken from iOS code into android equivalent code:
You should use X509EncodedKeySpec to convert String to Public Key. Here is the code.
val keySpecPublic = X509EncodedKeySpec(Base64.decode(publicKeyString, Base64.DEFAULT))
val publicKey = KeyFactory.getInstance("RSA").generatePublic(keySpecPublic) as RSAPublicKey
Hope it will work.

CryptoJS AES encryption and Java AES decryption

I'm only asking this because I have read many posts for 2 days now about crypto AES encryption, and just when I thought I was getting it, I realized I wasn't getting it at all.
This post is the closest one to my issue, I have exactly the same problem but it is unanswered:
CryptoJS AES encryption and JAVA AES decryption value mismatch
I have tried doing it in many ways but I haven't gotten it right.
First Off
I'm getting the already encrypted string (I only got the code to see how they were doing it), so modifying the encryption way is not an option. That's why all the similar questions aren't that useful to me.
Second
I do have access to the secret key and I can modify it (so adjusting length is an option if neccessary).
The encryption is done on CryptoJS and they send the encrypted string as a GET parameter.
GetParamsForAppUrl.prototype.generateUrlParams = function() {
const self = this;
return new Promise((resolve, reject) => {
const currentDateInMilliseconds = new Date().getTime();
const secret = tokenSecret.secret;
var encrypted = CryptoJS.AES.encrypt(self.authorization, secret);
encrypted = encrypted.toString();
self.urlParams = {
token: encrypted,
time: currentDateInMilliseconds
};
resolve();
});
};
I can easily decrypt this on javascript using CryptoJS with:
var decrypted = CryptoJS.AES.decrypt(encrypted_string, secret);
console.log(decrypted.toString(CryptoJS.enc.Utf8));
But I don't want to do this on Javascript, for security reasons, so I'm trying to decrypt this on Java:
String secret = "secret";
byte[] cipherText = encrypted_string.getBytes("UTF8");
SecretKey secKey = new SecretKeySpec(secret.getBytes(), "AES");
Cipher aesCipher = Cipher.getInstance("AES");
aesCipher.init(Cipher.DECRYPT_MODE, secKey);
byte[] bytePlainText = aesCipher.doFinal(byteCipherText);
String myDecryptedText = = new String(bytePlainText);
Before I had any idea of what I was doing, I tried base64 decoding, adding some IV and a lot of stuff I read, of course none of it worked.
But after I started to understand, kinda, what I was doing, I wrote that simple script above, and got me the same error on the post: Invalid AES key length
I don't know where to go from here. After reading a lot about this, the solution seems to be hashing or padding, but I have no control on the encryption method, so I can't really hash the secret or pad it.
But as I said, I can change the secret key so it can match some specific length, and I have tried changing it, but as I'm shooting in the dark here, I don't really know if this is the solution.
So, my question basically is, If I got the encrypted string (in javascript like the first script) and the secret key, is there a way to decrypt it (in Java)? If so, how to do it?
Disclaimer: Do not use encryption unless you understand encryption concepts including chaining mode, key derivation functions, IV and block size. And don't roll your own security scheme but stick to an established one. Just throwing in encryption algorithms doesn't mean an application has become any more secure.
CryptoJS implements the same key derivation function as OpenSSL and the same format to put the IV into the encrypted data. So all Java code that deals with OpenSSL encoded data applies.
Given the following Javascript code:
var text = "The quick brown fox jumps over the lazy dog. 👻 👻";
var secret = "René Über";
var encrypted = CryptoJS.AES.encrypt(text, secret);
encrypted = encrypted.toString();
console.log("Cipher text: " + encrypted);
We get the cipher text:
U2FsdGVkX1+tsmZvCEFa/iGeSA0K7gvgs9KXeZKwbCDNCs2zPo+BXjvKYLrJutMK+hxTwl/hyaQLOaD7LLIRo2I5fyeRMPnroo6k8N9uwKk=
On the Java side, we have
String secret = "René Über";
String cipherText = "U2FsdGVkX1+tsmZvCEFa/iGeSA0K7gvgs9KXeZKwbCDNCs2zPo+BXjvKYLrJutMK+hxTwl/hyaQLOaD7LLIRo2I5fyeRMPnroo6k8N9uwKk=";
byte[] cipherData = Base64.getDecoder().decode(cipherText);
byte[] saltData = Arrays.copyOfRange(cipherData, 8, 16);
MessageDigest md5 = MessageDigest.getInstance("MD5");
final byte[][] keyAndIV = GenerateKeyAndIV(32, 16, 1, saltData, secret.getBytes(StandardCharsets.UTF_8), md5);
SecretKeySpec key = new SecretKeySpec(keyAndIV[0], "AES");
IvParameterSpec iv = new IvParameterSpec(keyAndIV[1]);
byte[] encrypted = Arrays.copyOfRange(cipherData, 16, cipherData.length);
Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding");
aesCBC.init(Cipher.DECRYPT_MODE, key, iv);
byte[] decryptedData = aesCBC.doFinal(encrypted);
String decryptedText = new String(decryptedData, StandardCharsets.UTF_8);
System.out.println(decryptedText);
The result is:
The quick brown fox jumps over the lazy dog. 👻 👻
That's the text we started with. And emojis, accents and umlauts work as well.
GenerateKeyAndIV is a helper function that reimplements OpenSSL's key derivation function EVP_BytesToKey (see https://github.com/openssl/openssl/blob/master/crypto/evp/evp_key.c).
/**
* Generates a key and an initialization vector (IV) with the given salt and password.
* <p>
* This method is equivalent to OpenSSL's EVP_BytesToKey function
* (see https://github.com/openssl/openssl/blob/master/crypto/evp/evp_key.c).
* By default, OpenSSL uses a single iteration, MD5 as the algorithm and UTF-8 encoded password data.
* </p>
* #param keyLength the length of the generated key (in bytes)
* #param ivLength the length of the generated IV (in bytes)
* #param iterations the number of digestion rounds
* #param salt the salt data (8 bytes of data or <code>null</code>)
* #param password the password data (optional)
* #param md the message digest algorithm to use
* #return an two-element array with the generated key and IV
*/
public static byte[][] GenerateKeyAndIV(int keyLength, int ivLength, int iterations, byte[] salt, byte[] password, MessageDigest md) {
int digestLength = md.getDigestLength();
int requiredLength = (keyLength + ivLength + digestLength - 1) / digestLength * digestLength;
byte[] generatedData = new byte[requiredLength];
int generatedLength = 0;
try {
md.reset();
// Repeat process until sufficient data has been generated
while (generatedLength < keyLength + ivLength) {
// Digest data (last digest if available, password data, salt if available)
if (generatedLength > 0)
md.update(generatedData, generatedLength - digestLength, digestLength);
md.update(password);
if (salt != null)
md.update(salt, 0, 8);
md.digest(generatedData, generatedLength, digestLength);
// additional rounds
for (int i = 1; i < iterations; i++) {
md.update(generatedData, generatedLength, digestLength);
md.digest(generatedData, generatedLength, digestLength);
}
generatedLength += digestLength;
}
// Copy key and IV into separate byte arrays
byte[][] result = new byte[2][];
result[0] = Arrays.copyOfRange(generatedData, 0, keyLength);
if (ivLength > 0)
result[1] = Arrays.copyOfRange(generatedData, keyLength, keyLength + ivLength);
return result;
} catch (DigestException e) {
throw new RuntimeException(e);
} finally {
// Clean out temporary data
Arrays.fill(generatedData, (byte)0);
}
}
Note that you have to install the Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy. Otherwise, AES with key size of 256 won't work and throw an exception:
java.security.InvalidKeyException: Illegal key size
Update
I have replaced Ola Bini's Java code of EVP_BytesToKey, which I used in the first version of my answer, with a more idiomatic and easier to understand Java code (see above).
Also see How to decrypt file in Java encrypted with openssl command using AES?.
When encrypting on one system and decrypting on another you are at the mercy of system defaults. If any system defaults do not match (and they often don't) then your decryption will fail.
Everything has to be byte for byte the same on both sides. Effectively that means specifying everything on both sides rather than relying on defaults. You can only use defaults if you are using the same system at both ends. Even then, it is better to specify exactly.
Key, IV, encryption mode, padding and string to bytes conversion all need to be the same at both ends. It is especially worth checking that the key bytes are the same. If you are using a Key Derivation Function (KDF) to generate your key, then all the parameters for that need to be the same, and hence specified exactly.
Your "Invalid AES key length" may well indicate a problem with generating your key. You use getBytes(). That is probably an error. You need to specify what sort of bytes you are getting: ANSI, UTF-8, EBCDIC, whatever. The default assumption for the string to byte conversion is the likely cause of this problem. Specify the conversion to be used explicitly at both ends. That way you can be sure that they match.
Crypto is designed to fail if the parameters do not match exactly for encryption and decryption. For example, even a one bit difference in the key will cause it to fail.

How to decrypt Triple Des CryptoJS values in Java class

I was asked to encrypt some text from client side ( web ) before sending it to server side ( java )
So i try to use CryptoJS library for client side.
I encrypt it like this :
var key = "aaaaaaaaaaaaaaaaaaaaaaaa";
var value = "KF169841";
var encryptedString = CryptoJS.TripleDES.encrypt(value, key);
console.log(encryptedString.toString());
And i get something like this : U2FsdGVkX19eYFFHgYGCr3v9/skTOKVp0pLWRNK9JTg=
I use this encryptedString and key in other Decrypt tool online ( Which also use CryptoJS ) and got back exact value KF169841.
After sending this value and key to server ( well key isn't sending directly to server though but for test, it is ), i need to decrypt it using Java.
But i quite don't know how to decrypt it. I'm tried some code from google search but it end up wrong padding if use DESese or get wrong value if i use ECB/NoPadding.
I did try to something like setting sfg for CryptoJS side like:
mode: CryptoJS.mode.EBC,
padding: CryptoJS.pad.NoPadding
But they got javascript exception ( a is not define )
So any have any experience with CryptoJS can help me decrypt this one using java ?
=============================================================
UPDATE : Sorry here my server side code i'm using
/**
* Method To Decrypt An Ecrypted String
*/
public String decrypt(String encryptedString, String myEncryptionKey) {
String decryptedText = null;
try {
byte[] keyAsBytes = myEncryptionKey.getBytes("UTF8");
KeySpec myKeySpec = new DESedeKeySpec(keyAsBytes);
SecretKeyFactory mySecretKeyFactory =
SecretKeyFactory.getInstance("DESede");
Cipher cipher = Cipher.getInstance("DESede/ECB/NoPadding");
SecretKey key = mySecretKeyFactory.generateSecret(myKeySpec);
cipher.init(Cipher.DECRYPT_MODE, key);
// BASE64Decoder base64decoder = new BASE64Decoder();
// byte[] encryptedText = base64decoder.decodeBuffer(encryptedString);
byte[] encryptedText = org.apache.commons.codec.binary.Base64.decodeBase64(encryptedString);
byte[] plainText = cipher.doFinal(encryptedText);
decryptedText= bytes2String(plainText);
} catch (Exception e) {
e.printStackTrace();
}
return decryptedText;
}
According to the documentation, your encryptedString variable contains structured data that must be split apart to be sent to Java code. You will need to send encryptedString.iv and encryptedString.ciphertext to your Java code. If you continue to use passwords (see below), you will need to send encryptedString.salt as well.
If you pass your key as a string it will be interpreted as a password and a key will be derived from it. If you actually want to pass an explicit key, follow the documentation and specify the IV and key as suggested by the code snippet below. If you stick with supplying a password, then you must figure out the derivation scheme and use the same process in your Java code.
// Code snippet from http://code.google.com/p/crypto-js/#Custom_Key_and_IV
<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes.js"></script>
<script>
var key = CryptoJS.enc.Hex.parse('000102030405060708090a0b0c0d0e0f');
var iv = CryptoJS.enc.Hex.parse('101112131415161718191a1b1c1d1e1f');
var encrypted = CryptoJS.AES.encrypt("Message", key, { iv: iv });
</script>
Regarding your Java code, it looks mostly OK (although there is plenty of room for error with string conversions). However, you probably want to convert your key from hex to binary rather than grabbing the bytes:
byte[] keyAsBytes = DatatypeConverter.parseHexBinary(myEncryptionKey);
This assumes you alter your JavaScript code to pass the literal key value.
You will also need to switch to DESede/CBC/PKCS5Padding and pass an IVParameterSpec object to your Cipher.init call, specifying the IV value sent from your Java Script code.

HMC SHA1 hash - Java producing different hash output than C#

This is a follow up to this question, but I'm trying to port C# code to Java instead of Ruby code to C#, as was the case in the related question. I am trying to verify the encrypted signature returned from the Recurly.js api is valid. Unfortunately, Recurly does not have a Java library to assist with the validation, so I must implement the signature validation myself.
Per the related question above (this), the following C# code can produce the hash needed to validate the signature returned from Recurly:
var privateKey = Configuration.RecurlySection.Current.PrivateKey;
var hashedKey = SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(privateKey));
var hmac = new HMACSHA1(hashedKey);
var hash = hmac.ComputeHash(Encoding.ASCII.GetBytes(dataToProtect));
return BitConverter.ToString(hash).Replace("-", "").ToLower();
Recurly provides the following example data on their signature documentation page:
unencrypted verification message:
[1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]
private key:
0123456789ABCDEF0123456789ABCDEF
resulting signature:
0f5630424b32402ec03800e977cd7a8b13dbd153-1312701386
Here is my Java implementation:
String unencryptedMessage = "[1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]";
String privateKey = "0123456789ABCDEF0123456789ABCDEF";
String encryptedMessage = getHMACSHA1(unencryptedMessage, getSHA1(privateKey));
private static byte[] getSHA1(String source) throws NoSuchAlgorithmException, UnsupportedEncodingException{
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] bytes = md.digest(source.getBytes("UTF-8"));
return bytes;
}
private static String getHMACSHA1(String baseString, byte[] keyBytes) throws GeneralSecurityException, UnsupportedEncodingException {
SecretKey secretKey = new SecretKeySpec(keyBytes, "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(secretKey);
byte[] bytes = baseString.getBytes("ASCII");
return Hex.encodeHexString(mac.doFinal(bytes));
}
However, when I print out the encryptedMessage variable, it does not match the message portion of the example signature. Specifically, I get a value of "c8a9188dcf85d1378976729e50f1de5093fabb78" instead of "0f5630424b32402ec03800e977cd7a8b13dbd153".
Update
Per #M.Babcock, I reran the C# code with the example data, and it returned the same output as the Java code. So it appears my hashing approach is correct, but I am passing in the wrong data (unencryptedMessage). Sigh. I will update this post if/when I can determine what the correct data to encrypt is- as the "unencrypted verification message" provided in the Recurly documentation appears to be missing something.
Update 2
The error turned out to be the "unencrypted verification message" data/format. The message in the example data does not actually encrypt to the example signature provided- so perhaps outdated documentation? At any rate, I have confirmed the Java implementation will work for real-world data. Thanks to all.
I think the problem is in your .NET code. Does Configuration.RecurlySection.Current.PrivateKey return a string? Is that value the key you expect?
Using the following code, .NET and Java return identical results.
.NET Code
string message = "[1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]";
string privateKey = "0123456789ABCDEF0123456789ABCDEF";
var hashedKey = SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(privateKey));
var hmac = new HMACSHA1(hashedKey);
var hash = hmac.ComputeHash(Encoding.ASCII.GetBytes(message));
Console.WriteLine(" Message: {0}", message);
Console.WriteLine(" Key: {0}\n", privateKey);
Console.WriteLine("Key bytes: {0}", BitConverter.ToString(hashedKey).Replace("-", "").ToLower());
Console.WriteLine(" Result: {0}", BitConverter.ToString(hash).Replace("-", "").ToLower());
Result:
Message: [1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]
Key: 0123456789ABCDEF0123456789ABCDEF
Key bytes: 4d857d2408b00c3dd17f0c4ffcf15b97f1049867
Result: c8a9188dcf85d1378976729e50f1de5093fabb78
Java
String message = "[1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]";
String privateKey = "0123456789ABCDEF0123456789ABCDEF";
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] keyBytes = md.digest(privateKey.getBytes("UTF-8"));
SecretKey sk = new SecretKeySpec(keyBytes, "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(sk);
byte[] result = mac.doFinal(message.getBytes("ASCII"));
System.out.println(" Message: " + message);
System.out.println(" Key: " + privateKey + "\n");
System.out.println("Key Bytes: " + toHex(keyBytes));
System.out.println(" Results: " + toHex(result));
Result:
Message: [1312701386,transactioncreate,[account_code:ABC,amount_in_cents:5000,currency:USD]]
Key: 0123456789ABCDEF0123456789ABCDEF
Key Bytes: 4d857d2408b00c3dd17f0c4ffcf15b97f1049867
Results: c8a9188dcf85d1378976729e50f1de5093fabb78
I suspect the default encoding of the values you're working on may be different. As they do not have it specified, they will use the default encoding value of the string based on the platform you're working on.
I did a quick search to verify if this was true and it was still inconclusive, but it made me think that strings in .NET default to UTF-16 encoding, while Java defaults to UTF-8. (Can someone confirm this?)
If such's the case, then your GetBytes method with UTF-8 encoding is already producing a different output for each case.
Based on this sample code, it looks like Java expects you to have not already SHA1'd your key before creating a SecretKeySpec. Have you tried that?

Categories

Resources