Reading Public Key Sent by Java Server in Swift - java

I'm trying to read a public key (x509 format encoded) from a Java Server to finalize my Elliptic Curve Diffie Hellman Exchange. I can send the Public Key to the server with no problem, but now I want to read the public Key that the server has sent to the iOS Client.
byte[] serverPubKeyEnc = serverKpair.getPublic().getEncoded(); (This is on the server)
This is what I return to the iOS side. For me to deal with it, I need to read it from the input stream and then turn it into a usable public key. This is what I have right now on the iOS side of things to read the key:
var error: Unmanaged<CFError>? = nil
let mutableData = CFDataCreateMutable(kCFAllocatorDefault, CFIndex(0))
if mutableData != nil
{
let headerSize = 26
//For importing Java key data
CFDataAppendBytes(mutableData, CFDataGetBytePtr(data as CFData), CFDataGetLength(data as CFData))
CFDataDeleteBytes(mutableData, CFRangeMake(CFIndex(0), headerSize))
//Use the mutableData here (SecKeyCreateWithData)
let publicKey = SecKeyCreateWithData(
mutableData!,
[
kSecAttrKeyType: kSecAttrKeyTypeEC,
kSecAttrKeyClass: kSecAttrKeyClassPublic,
] as NSDictionary,
&error)
let fullKey = SecKeyCopyExternalRepresentation(publicKey!, &error)
return fullKey!
}
Here I can read the "publicKey", I know it has some value inside it. How can I turn this into a usable key for me to generate the shared secret?
TLDR: I want to read a public key that comes from the Java server (ECDH) to generate a symmetric key for encryption in the iOS client.

The complete flow would look like:
receiving 91 bytes with the public key from server side
create a SecKey with SecKeyCreateWithData
create a key pair on iOS with SecKeyCreateRandomKey
send own public key to server side
server side can compute the shared secret with that information
client computes a shared secret with SecKeyCopyKeyExchangeResult
if everything is correct, it should give the same shared secret on iOS and Java side
Therefore, to get a complete test case, one can write a Java program that generates a key pair. For simplicity, one can copy/paste the public key between the Java and iOS app for a test instead of using a network connection. The Java program writes the public key to the console. This key is copied into the Swift source code. The Swift program is compiled and generates a key pair as well. The public key is copied / pasted to the Java program, which reads it on the console. Both programs then output the calculated shared secret, which should be the same for obvious reasons, since it is used for further symmetric encryption.
This fine answer https://stackoverflow.com/a/26502285/2331445 provides utility methods for converting a hex String to Data and back.
iOS Swift Code
The following code assumes that a secp256r1 curve is used with a key size of 256 bit.
The described flow could be implemented as follows:
let otherKey = "3059301306072a8648ce3d020106082a8648ce3d03010703420004df96b3c0c651707c93418781b91782319f6e798550d954c46ac7318c7eac130f96380991a93049059e03e4190dd147b64d6ebc57320938f026844bda3de22352".hexadecimal!
guard let otherPublicKey = otherPublicKey(data: otherKey) else { return }
guard let ownPrivateKey = createOwnKey() else { return }
guard let ownPublicKey = SecKeyCopyPublicKey(ownPrivateKey) else { return }
send(ownPublicKey: ownPublicKey)
if let sharedSecret = computeSharedSecret(ownPrivateKey: ownPrivateKey, otherPublicKey: otherPublicKey) {
print("shared secret: \(sharedSecret.hexadecimal)")
} else {
print("shared secret computation failed")
}
The used functions:
private func otherPublicKey(data: Data) -> SecKey? {
var error: Unmanaged<CFError>? = nil
let cfData = data.dropFirst(26) as CFData
let attributes = [
kSecAttrKeyType: kSecAttrKeyTypeEC,
kSecAttrKeyClass: kSecAttrKeyClassPublic,
] as CFDictionary
if let publicKey = SecKeyCreateWithData(cfData, attributes, &error) {
return publicKey
}
print("other EC public: \(String(describing: error))")
return nil
}
private func createOwnKey() -> SecKey? {
var error: Unmanaged<CFError>? = nil
let keyPairAttr: [String : Any] = [kSecAttrKeySizeInBits as String: 256,
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
kSecPrivateKeyAttrs as String: [kSecAttrIsPermanent as String: false]
]
guard let key = SecKeyCreateRandomKey(keyPairAttr as CFDictionary, &error) else {
print("key creation: \(String(describing: error))")
return nil
}
return key
}
This function send only outputs the key in hex on the debug console. For the test it can be transferred to the Java program via copy/paste. In a real program it would be transferred to the server via a network connection.
private func send(ownPublicKey: SecKey) {
guard let data = SecKeyCopyExternalRepresentation(ownPublicKey, nil) as Data? else {
print("SecKeyCopyExternalRepresentation failed")
return
}
let secp256r1Header = "3059301306072a8648ce3d020106082a8648ce3d030107034200"
let pkWithHeader = secp256r1Header + data.hexadecimal
print("ownPublicKeyHexWithHeader \(pkWithHeader.count / 2) bytes: " + pkWithHeader)
}
With the own private key and the public key of the server, the shared secret can be computed.
private func computeSharedSecret(ownPrivateKey: SecKey, otherPublicKey: SecKey) -> Data? {
let algorithm:SecKeyAlgorithm = SecKeyAlgorithm.ecdhKeyExchangeStandard
let params = [SecKeyKeyExchangeParameter.requestedSize.rawValue: 32, SecKeyKeyExchangeParameter.sharedInfo.rawValue: Data()] as [String: Any]
var error: Unmanaged<CFError>? = nil
if let sharedSecret: Data = SecKeyCopyKeyExchangeResult(ownPrivateKey, algorithm, otherPublicKey, params as CFDictionary, &error) as Data? {
return sharedSecret
} else {
print("key exchange: \(String(describing: error))")
}
return nil
}
Test
In the upper area you can see the Xcode console and in the lower area the output of the Java program. The common secret is the same. So the test was successful.

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?

Encrypting and Decrypting data from App to server, ionic/angular -> java server

I want to encrypted data in ionic, send it to server by a POST request and decrypted on server side. And also received encrypted data from server and decrypted on the app.
The one I'm currently testing is AES256, but it seems that the encrypted data is different. Which means that it isn't encrypted or decrypted the correct way once that it gives 2 different values. DO you know some way to make it possible?
encrypt(secureKey, secureIV, data) {
this.platform.ready().then(() => {
cordova.plugins.AES256.encrypt(secureKey, secureIV, data,
(encrypedData) => {
console.log('Encrypted Data----', encrypedData);
this.decrypt(this.secureKey, this.secureIV, encrypedData);
}, (error) => {
console.log('Error----', error);
});
});
}
decrypt(secureKey, secureIV, encryptedData) {
this.platform.ready().then(() => {
cordova.plugins.AES256.decrypt(secureKey, secureIV, encryptedData,
(decryptedData) => {
console.log('Decrypted Data----', decryptedData);
}, (error) => {
console.log('Error----', error);
});
});
}
Using this methods on ionic to encrypt and decrypt strings of code. But The value of those on android device is different from the server running on java. How can I make it, to run the same processes for encrypting and decrypting data on the device as on the server?
UPDATE / SOLVED
So, I ignore the previous plugin of AES 256 of cordova, and install CryptoJS libraryCryptoJS library.
this was the result:
var key = await CryptoJS.enc.Latin1.parse("12345678910123456789012345678901");
var iv = await CryptoJS.enc.Latin1.parse("1234567891123456");
console.log(key + " ; " + iv)
var encrypted = await CryptoJS.AES.encrypt("test", key, { iv: iv },
{
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs5
});
console.log("encrypted -> " + encrypted.ciphertext.toString(CryptoJS.enc.Base64))
I had to use .Latin1.parse in order to generate the same value in TypeScript/JavaScript as in java. More info, here

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.

Go Hmac SHA1 generates hash different from Hmac SHA1 in Java

I'm just starting to learn Go and I'm trying to rewrite my existing small application from Java to Go.
I need to create Base64 hash of input string with key using Hmac SHA1 algorithm.
My Java code:
private String getSignedBody(String input, String key) {
String result = "";
try {
SecretKeySpec signingKey = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(input.getBytes("UTF-8"));
result = Base64.encodeToString(rawHmac, false);
} catch (Exception e) {
Logger.error("Failed to generate signature: " + e.getMessage());
}
return result;
}
My Go code:
func GetSignature(input, key string) string {
key_for_sign := []byte(key)
h := hmac.New(sha1.New, key_for_sign)
h.Write([]byte(input))
return base64.StdEncoding.EncodeToString(h.Sum(nil))
}
The problem is that Go code generates output that is not expected. For example, for input string "qwerty" and key "key" Java output will be RiD1vimxoaouU3VB1sVmchwhfhg= and Go output will be 9Cuw7rAY671Fl65yE3EexgdghD8=.
Where did I make mistakes in the Go code?
The Go code you provided gives exactly the same output as the Java code.
Try it on the Go Playground.
Output:
RiD1vimxoaouU3VB1sVmchwhfhg=
You made the mistake when you called your GetSignature() function. Call it like the linked example code:
fmt.Println(GetSignature("qwerty", "key"))
Your mistake was that you passed an empty input to your GetSignature() function. Calling it with empty "" input and "key" key produces the non-expected output you provided:
fmt.Println(GetSignature("", "key"))
Output:
9Cuw7rAY671Fl65yE3EexgdghD8=

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.

Categories

Resources