Data signed on iOS can't be verified in Java - java

I have some data that I'm signing on iOS with SecKeyRawSign using Elliptic Curve private key. However, verifying that data in Java using Signature.verify() returns false
The data is a random 64 bit integer, split into bytes like so
uint64_t nonce = (some 64 bit integer)
NSData *nonceData = [NSData dataWithBytes: &nonce length: sizeof(nonce)];
From that data I'm creating a SHA256 digest
int digestLength = CC_SHA256_DIGEST_LENGTH;
uint8_t *digest = malloc(digestLength);
CC_SHA256(nonceData.bytes, (CC_LONG)nonceData.length, digest);
NSData *digestData = [NSData dataWithBytes:digest length:digestLength];
and then signing it with private key
size_t signedBufferSize = kMaxCipherBufferSize;
uint8_t *signedBuffer = malloc(kMaxCipherBufferSize);
OSStatus status = SecKeyRawSign(privateKeyRef,
kSecPaddingPKCS1SHA256,
(const uint8_t *)digestData.bytes,
digestData.length,
&signedBuffer[0],
&signedBufferSize);
NSData *signedData = nil;
if (status == errSecSuccess) {
signedData = [NSData dataWithBytes:signedBuffer length:signedBufferSize];
}
Everything appears to work fine.
Then, in Java server, I'm trying to verify that signed data
PublicKey publicKey = (a public key sent from iOS, X509 encoded)
Long nonce = (64 bit integer sent from iOS)
String signedNonce = (base64 encoded signed data)
ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
buffer.putLong(nonce);
byte[] nonceBytes = buffer.array();
byte[] signedNonceBytes = Base64.getDecoder().decode(signedNonce.getBytes());
Signature signer = Signature.getInstance( "SHA256withECDSA" );
signer.initVerify( publicKey );
signer.update( nonceBytes );
Boolean isVerified = signer.verify( signedNonceBytes );
At this point, signer.verify() returns false
I also tried to sign plain data, instead of SHA256 digest, but that doesn't work either.
What am I missing? Am I signing the data correctly? Am I using correct padding? Is there something else to be done with data to be able to verify it with SHA256withECDSA algorithm?

The byte ordering does not match:
iOS is little endian. The way you create nonceData, this order is retained.
On the Java side, ByteBuffer defaults to big endian, independent of the underlying operating system / hardware.
So you need to change the byte order:
ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
buffer.order(ByteOrder.LITTLE_ENDIAN);
buffer.putLong(nonce);

I'm a java guy, so I can't say anything about the iOS side, but a quick check of the java side can be done using the commented assumptions:
// Generate a new random EC keypair for testing
KeyPair keys = KeyPairGenerator.getInstance("EC").generateKeyPair();
PrivateKey privateKey = keys.getPrivate();
PublicKey publicKey = keys.getPublic();
// Generate a Random nonce to test with
byte[] nonceBytes = new byte[8]; // (some 64 bit integer)
new Random(System.nanoTime()).nextBytes(nonceBytes);
// sign
Signature sign = Signature.getInstance("SHA256withECDSA");
sign.initSign(privateKey);
sign.update(nonceBytes);
byte[] signature = sign.sign();
//verify
Signature verify = Signature.getInstance("SHA256withECDSA");
verify.initVerify(publicKey);
verify.update(nonceBytes);
Boolean isVerified = verify.verify(signature);
// print results
System.out.println("nonce used ::" + Base64.getEncoder().encodeToString(nonceBytes));
System.out.println("Signed nonce ::" + Base64.getEncoder().encodeToString(signature));
System.out.println("nonce used ::" + isVerified);
As you'd expect returns, the code above will always return that the signature is verified. Check your assumptions are accurate and validate the keys being used are correct on both sides.

I can advice you to use some Crypto Library which is available for both iOS and JAVA sides (f.e.:https://github.com/VirgilSecurity/virgil-crypto). This will ensure that the algorithm and block types (etc.) are the same in both cases and you won't need to worry about it anymore. I believe you will finds many crypto libraries on a GitHub.

In getBytes() you could specify the encoding technique using java.nio.charset.StandardCharsets. and do the same with the decoder.
https://docs.oracle.com/javase/7/docs/api/java/nio/charset/StandardCharsets.html

Related

Decrypting Payment Data with Visa Checkout

I am getting information back from Visa Checkout in an encrypted format. The guide on their site provides these instructions:
First, you must decrypt the dynamic key (encKey), then use the decrypted dynamic key value to decrypt the payment data payload (encPaymentData).
Follow these four steps to decrypt the encKey:
Base64-decode the encKey.
Remove the first 32 bytes of the decoded value. This is the HMAC (Hash Message Authentication Code). Calculate a SHA-256 HMAC of the
rest of the decoded data using your API Shared Secret and compare it
to the HMAC from the first 32 bytes.
The next 16 bytes should be removed and used as the IV (Initialization Vector) for the decryption algorithm.
Decrypt the remaining data using AES-256-CBC, the IV from step 3, and the SHA-256 hash of your API Shared Secret.
Follow these four steps to decrypt the encPaymentData using the
decrypted encKey:
Base64-decode the encPaymentData.
Remove the first 32 bytes of the decoded value. This is the HMAC. Calculate a SHA-256 HMAC of the rest of the decoded data using
the
decrypted encKey and compare it with the HMAC from the first 32
bytes.
The next 16 bytes should be removed and used as the IV for the decryption algorithm.
Decrypt the rest of the encPaymentData payload using AES-256-CBC, the IV from step 3, and the SHA256-hash of the
decrypted encKey.
I tried using ColdFusion but I am lost somewhat with the encryption issues, and am unable to fix the code. Below I have what is required. I am stuck on the step 3 & 4 where they say compare it and then decrypt it. Can someone guide what could be done to fix it?
enckey:
2M2WWOD4wANsGwWTmPqQIQYdz9WPwgiR0ntJHGaxm23b5a1sWUtCBcUQUMMtc9hvcYavJ6yqPgETOGZgDOdd9qjDwIb2aV9DLZT1iIcB3zNN5Ddhxd9iiui6TAlJxU/O
encPaymentData:
X2TXp0ZmwHrtfzSP5TPjUOjdZb0rjsHeDSqr8TwIF/VR8sMQhWN5hP4IRhQxWT CZcQhxZoUHP 0g/E/ot sjREAJ8YQf7c7jSzKsXRH/wrew5rQit2wJBlVSSZ YoLeIHbLrTz CfIoFv09hixl7ff27u0YCyV0zjP5vNfCBEIwfqyriqwXK2J QEOxQiKzDUW4br3o1t31aymCQC9eBBpoVKjFfSKlNXM9QEdNZBcLMZ8Wlv8lF/ua bnwshbM9u7Uhudqvut94RZEW NzkRD8MfBo12e/XhnL35qxGpHeQNPClC4EQDK6U/HmegeOj BZLbIIYBs6t9E8Q3AKBwfiPOFgB gSVnhXKnd3nKvllaG BaGrQJtk 7QAtnHMHxQAO5rdiS9465HCdiHa8zlv7SkvWh8EwcKCiT4qiZSM6QuYAeRSzDpPS1gsZ54Q9LizUnueH7yyzSd47cLPd0VlOQxobKtNN2LrsRb3IwOfzuwGnSRf2cNp49hBmmGP1b0BC hhB6UpCqP2ixTPvui NwMYzqZUe336bF1mfnKzEbEZIvIrPyx3uMiLDAns2g7S80gMNnHb/09i49xbfY3V7oudeiHV99FCh67DuG3uHE3/HzIZbcnxJwVJoJj6/3DuzK/Kw1JqSorE0M1qxUqoNkJC4aNCBrqfTlR7/eErrvB554TUZwcyQXqKCwrKv4NJEw6S0n3W1VASkfA0atbJQX2aLgx9kqnhYdDbaU8UcFIoeA45 yEuQ9vXzo2ILQhvamsAAFQd3i4mEOZ KNtMu25dDFlORn5C/oTZ1t1dzJoYMvq44gejp6L3IK e7JCugGchr963a2kd8NFa3wctRDHF8ChHxawVlU0aY7nasrVireMFLiM 9XIb4abfDtct/j1Q8IGN0hRkgCHO6dlnOrAaaQDYYH3axaMDp5Onb04whULYqGbn/XSR8Sn8gnoFbYqVJbR5aCp5Pe9TpfwxlEvV3z8ZfsASqW2y So9gpwg2y16K/FX3Io6kqeqUlAxbTRDfN/ofDIJaO H PUu2teqjvwvCkjPciGOQdXT5JxqoCiHqRwD0zeZPcG3b9Nfrq3Caf6zjwhf /CMeGc3dNHhSkXox R50MP8BlLWk/bXZRScTE/HSrVxE n073utHAnbVOM3gVha0Hr8TmoV8z1vBg5fY253so6kQX61ZIfHneCAZo0qeKRgDgLUryVPmUNc5 yKP8DxtmHl/0YUztpgyEx5njsrn1L 3EHMMUhui8d LQdNZoEpZ9U1Xb7XVsV5gnwR/mOITNOKJQsine4zMMHBcomHclrM0CuI58YrKPqworCmK6CYfzSc8UmXxXUe5dzND/DS9XgqDttQic2/OqTSAK63ynnrNqzr3D56VpDBeDeQjk3mc/0zmuFAPEXoAQoQKfD6HEuajvWJebQ6QIPgA TshqsnPlktbpftr4lsuB1tHS/W8D7SYVFMC/Kxy9QuYWs0cmRTtzfWEKIRHeDElOTQCX5JB5PgzVhhi5kYTi488Ba8j4zvNUw55hEoMxONYO7eMjJosmNjULsT492LGw3EfAgmgx9h3yFLQRZgfylg0h4PfLlcPOAdsnVX9/yLElD xu7Atwc4S7pBWTHvwue7PpRvWpTeqkU5sqiX4KcV5x8rk mBtxm48a8fsmp GNf 4IjwXu9cQaU9WLipiEnkqFsYo7/aAsmmKWBETyQg9BFXYK 165vrzSX8WTsv6ZZDnVjcE1n4Ov8Jl2cnAigoQbB0ROPpIRzZ3zH2diUv1vzlSuh9gbEJf3uQRKlYRVUbpboC0RbQ/7jgznfJAWyLykyDQ0EB8fVEOtbP1l4JEz39QwAU18ph3btnWWuKEV4 ghYvNG4m1DYntSF57s2ajRS6rPtR oYvGjrJL9zbHBhKHlfkIPC0TKotOCi96mqpikbBEfIZSomHxYgDwYCSvt60zaDIjlBxZ1UBdK JL0554Wia9W3Wg91bmYS9Q4SXMT8r4xGYB7OutEV24n7p088rVm/w2SZSiqlLqai539k6WGkzEQf19ytPtIE81a N z7aijTjy 7FCuVPF90svI5/NoGpSINqv84HUcMU71BvXUIT53Ea6CCpiWvvOPpo/XZar44emlIG0UgeB kfP6C6sis=
Secret code:
zRf7WZ3nM7ON{U0E6J5S}KpVm#k2ReDyq#1lG9go
CF Code:
<cfset str = "2M2WWOD4wANsGwWTmPqQIQYdz9WPwgiR0ntJHGaxm23b5a1sWUtCBcUQUMMtc9hvcYavJ6yqPgETOGZgDOdd9qjDwIb2aV9DLZT1iIcB3zNN5Ddhxd9iiui6TAlJxU/O">
<cfset tobas = tobase64(str)>
<cfset getFirst32bytes = Left(tobas,32)>
<cfset tobas2 = RemoveChars(tobas,1,32)>
<cfdump var="#tobas2#">
<cfset key = "zRf7WZ3nM7ON{U0E6J5S}KpVm#k2ReDyq##1lG9go">
<cfset x = hmac("#tobas2#","#key#","HMACSHA256")>
<cfset y = hmac("#getFirst32bytes#","#key#","HMACSHA256")>
<cfset decalgo = Left(x,16)>
<cfset decremainingData = RemoveChars(x,1,16)>
<cfset getDec = Decrypt(decalgo,"#key#","AES")>
<cfdump var="#x#"><br>
<cfdump var="#y#"><br>
<cfdump var="#decalgo#">
<cfdump var="#decremainingData#">
<cfdump var="#getDec#">
This is the java example they have on their site:
private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
private static final String HASH_ALGORITHM = "SHA-256";
private static final String HMAC_ALGORITHM = "HmacSHA256";
private static final int IV_LENGTH = 16, HMAC_LENGTH = 32;
private static final Charset utf8 = Charset.forName("UTF-8");
private static final Provider bcProvider;
static {
bcProvider = new BouncyCastleProvider();
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.addProvider(bcProvider);
}
}
private static byte[] decrypt(byte[] key, byte[] data) throws GeneralSecurityException {
byte[] decodedData = Base64.decode(data);
if (decodedData == null || decodedData.length <= IV_LENGTH) {
throw new RuntimeException("Bad input data.");
}
byte[] hmac = new byte[HMAC_LENGTH];
System.arraycopy(decodedData, 0, hmac, 0, HMAC_LENGTH);
if (!Arrays.equals(hmac,
hmac(key, decodedData, HMAC_LENGTH, decodedData.length– HMAC_LENGTH))) {
throw new RuntimeException("HMAC validation failed.");
}
byte[] iv = new byte[IV_LENGTH];
System.arraycopy(decodedData, HMAC_LENGTH, iv, 0, IV_LENGTH);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM, bcProvider);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(hash(key), "AES"),
new IvParameterSpec(iv));
return cipher.doFinal(decodedData, HMAC_LENGTH + IV_LENGTH,
decodedData.length– HMAC_LENGTH– IV_LENGTH);
}
private static byte[] hash(byte[] key) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance(HASH_ALGORITHM);
md.update(key);
return md.digest();
}
private static byte[] hmac(byte[] key, byte[] data, int offset, int length)
throws GeneralSecurityException {
Mac mac = Mac.getInstance(HMAC_ALGORITHM, bcProvider);
mac.init(new SecretKeySpec(key, HMAC_ALGORITHM));
mac.update(data, offset, length);
return mac.doFinal();
}
An important thing to understand about the sample code is that it refers to bytes. Your CF code is using characters. That might seem like a trivial distinction, but they are totally different things, which will produce very, very different results. In order to decrypt successfully, you need to work with the bytes (or binary) of the given strings - not characters.
Although it is possible to manipulate binary arrays using core CF functions, like arraySlice(), the syntax gets a little bulky/clunky at times. The reason is that binary arrays are a different type of object than your standard CF array, i.e. byte[] versus java.util.List. So depending on which functions are used, you may need javacast to coerce variables into the expected type. With that in mind ..
Part I - Decrypt the encKey
Base64-decode the encKey.
Remove the first 32 bytes of the decoded value. This is the HMAC (Hash Message Authentication Code). Calculate a SHA-256 HMAC of the
rest of the decoded data using your API Shared Secret and compare it
to the HMAC from the first 32 bytes.
First convert the base64 string into binary using binaryDecode. Then extract the appropriate number of bytes from the returned array. This is the expected HMAC value:
hmacSize = 32;
binaryToDecrypt = binaryDecode(encryptedKey, "base64");
expectedHMAC = binaryEncode( javacast("byte[]", arraySlice(binaryToDecrypt, 1, hmacSize))
, "hex" );
Next, extract all of the remaining bytes, and use them to calculate the actual HMAC. Verify it against the expected value. If the two do not match, something went wrong.
remainData = arraySlice(binaryToDecrypt, hmacSize + 1);
actualHMAC = hmac( javacast("byte[]", remainData ), sharedSecret, "HMACSHA256");
if (compare(actualHMAC, expectedHMAC) != 0) {
throw("ERROR: Invalid HMAC ["& actualHMAC &"]. Expected ["& expectedHMAC &"]");
}
The next 16 bytes should be removed and used as the IV (Initialization Vector) for the decryption algorithm.
The remaining bytes contains an IV, followed by the encrypted value. Before you can decrypt the latter, you need to extract and separate the two:
ivSize = 16;
ivValue = javacast("byte[]", arraySlice(remainData, 1, ivSize));
encryptedValue = javacast("byte[]", arraySlice(remainData, ivSize + 1));
Decrypt the remaining data using AES-256-CBC, the IV from step 3, and the SHA-256 hash of your API Shared Secret.
The last step before you can decrypt is to generate the decryption key, by hashing the shared secret. Unfortunately, CF's hash() function always returns a hex string. So it must be converted into base64 format to be compatible with the decryption function.
keyHex = hash(sharedSecret, "SHA-256", "utf-8");
keyBase64 = binaryEncode(binaryDecode(keyHex, "hex"), "base64");
Finally, use all three values to decrypt. The returned binary will contain the encryption key used in part II.
decryptedKeyBinary = decryptBinary( encryptedValue
, keyBase64
, "AES/CBC/PKCS5Padding"
, ivValue);
Part II - Decrypt the encPaymentData
Use the exact same process as in Part I, just swap the variables:
Use encPaymentData instead of encryptedKey
Use decryptedKeyBinary instead of sharedSecret.
The final, decrypted result will be binary. Use charsetEncode to convert it back into a human readable string:
result = charsetEncode(decryptedResult, "utf-8");
NB: The sample values you posted appear to be broken, as they do not even work with the java example. The steps above do produce the correct result when used valid values (key, data, etcetera).

RSA transfer iOS to Java

I want to encrypt a String in Objective-C (iOS) and decrypt it after HTTP Transfer in Java.
The Public Key was successfully transmitted from Java to iOS (at least it is recognied in the Keychain)
In ObjC I try to send the text "Hallo" using this:
NSData* myData = [#"Hallo" dataUsingEncoding:NSUTF8StringEncoding];
OSStatus status = noErr;
size_t cipherBufferSize;
uint8_t *cipherBuffer;
cipherBufferSize = SecKeyGetBlockSize(publicKey);
cipherBuffer = malloc(cipherBufferSize);
size_t dataLength = [self length];
uint8_t* intDataToEncrypt = (uint8_t*)[self bytes];
if (cipherBufferSize < dataLength) {
printf("Could not encrypt. Packet too large.\n");
return NULL;
}
status = SecKeyEncrypt( publicKey,
kSecPaddingNone,
intDataToEncrypt,
dataLength,
cipherBuffer,
&cipherBufferSize
);
NSData *encryptedData = [NSData dataWithBytes:cipherBuffer length:cipherBufferSize];
"encryptedData" is then send base64 encoded via HTTP Post to a Java Servlet which uses this (BouncyCastle is used and the data converted back from Base64 first, the key is read from a file):
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(b64.decodeBuffer(key));
PrivateKey privateKey = kf.generatePrivate(keySpec);
Cipher cipher = Cipher.getInstance("RSA/NONE/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] myData = cipher.doFinal(byteData, 0, 256);
The output is completely messed up when trying to use as new String(myData, "UTF-8");
I tried sending only the "Hallo" without encryption this works, so it seems base64 is ok.
When I encrypt the same phrase in Java directly the output differs in 2 Bytes from the ObjC encryption.
In the End I want to use OAEP Padding instead of none...
I hope someone can help with this, the iOS Security Framework seems really to be very bad
The Problem was not in the RSA part but the Base64 encoding and transfer. All "+" in the String were received as " " due to HTTP(S) transfer (of course, I should have thought of that).
Replacing them with "-" as proposed by the unofficial Base64URL version solves the problem. (also all "/" with "_")
This also explain why only 2 bytes were different - those were the "+" symbol bytes.

iOS symmetric key encryption / decryption equivalent to Java

I am trying to encrypt/decrypt data ios to java & java to ios but I data encrypted in java is not properly decrypted in ios & data encrypted in ios is not properly decrypted in java
- (NSData *) encrypt:(NSData *) dataToEncrypt symmetricKey:(NSData *)symmetricKey context:(CCOperation)encryptOrDecrypt{
NSUInteger data_length= [dataToEncrypt length];
uint8_t input_raw_data[data_length];
//The [dataToEncrypt length] gives the number of chars present in the string.So say there are 10 chars.
//Now,the getBytes needs to get the raw bytes from this i.e. binary NSData.But suppose the encoding was
//full 16 bit encoding then the number of bytes needed wd have been double- 20.But as we are using the
//NSUTF8StringEncoding,the number of byes needed is 1 per char as the chars(even if originally unicode are
//compressed into an 8 bit UTF8 encoding.)
[dataToEncrypt getBytes:&input_raw_data length:data_length];
// [dataToEncrypt getBytes:&input_raw_data maxLength:data_length usedLength:NULL encoding:NSUTF8StringEncoding options:0 range:NSMakeRange(0,data_length) remainingRange:NULL];
//According to the doc: For block ciphers, the output size will always be less than or
//equal to the input size plus the size of one block.
//That's why we need to add the size of one block here
size_t buffer_size = data_length + kCCBlockSizeAES128;
void* buffer = malloc(buffer_size);
size_t num_bytes_encrypted = 0;
CCCryptorStatus crypt_status = CCCrypt(encryptOrDecrypt, kCCAlgorithmAES128, 0x0000,
[symmetricKey bytes], kCCKeySizeAES128,
NULL,
input_raw_data, data_length,
buffer, buffer_size,
&num_bytes_encrypted);
// NSLog(#"~~num bytes encrypted: %d",num_bytes_encrypted);
if (crypt_status == kCCSuccess){
NSLog(#"~~Data encoded successfully...");
return [NSData dataWithBytesNoCopy:buffer length:num_bytes_encrypted];
}
free(buffer); //free the buffer;
return nil;
}
I have used this
Java Code -
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
String keyString = "keykeykeykeykeykeykeykey";
byte[] keyBytes = keyString.getBytes("UTF-8");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyBytes, "AES"), new IvParameterSpec(new byte[16]));
byte[] resultBytes = cipher.doFinal("Hallo Welt!".getBytes("UTF8"));
FileOutputStream out = new FileOutputStream(new File("encryptedFileJava"));
out.write(resultBytes); out.close();
and this is encrypted text - “Se áJbë|8”R ,
key - BW3dKDf2bkDC4Bq9xTdr1g==
Please help me or suggest me any solution.
Thank you.
You have at least two problems:
The Objective C code is using ECB mode, while the Java code is using CBC mode. Use a bytearray of zeroes instead of NULL in the CCrypt-invocation to use CBC mode with a zero IV like the Java code.
Since keyBytes is 24 bytes long, Java will use AES-192. CCrypt will just ignore the extra bytes. Either specify AES-192 to CCrypt or use a 128 bit key ("keykeykeykeykeyk" should work).
For secured communication between IOS and Java devices, symmetric key encryption can be used.
In such cases where the platforms are different it is advisable that the key generated is a plain text key and not a API generated key.
AES 128 bit encryption can be used in such cases. IOS devices are capable of generating a symmetric key and encrypting the text using AES encryption.
Below link provides the java code to encrypt and decrypt using plain text symmetric key
http://www.java-redefined.com/2015/06/symmetric-key-encryption-ios-java.html

How Do i compress or encode the Elliptic curve public key and put it over the network?

I am developing Distributed digital signature that signs a document and send it through network to the Application Server.I am using socket programming in java to do it. I think the public key should be encoded or compressed i.e the x and y values are somehow represented as a single binary data and saved in a public registry or network.But i don't know how to do it in java.
// I have class like this
public class CryptoSystem{
EllipticCurve ec = new EllipticCurve(new P192());
//-------------------
//--------------------
public ECKeyPair generatekeyPair()
{
return ECKeyPair(ec);
}
}
// i don't think i have problem in the above
CryptoSystem crypto = new CryptoSystem();
ECKeyPair keyPair = crypto.generateKeyPair();
BigInteger prvKey = keyPair.getPrivateKey();
ECPoint pubKey = keyPair.getPublicKey();
// recommend me here to compress and send it the public key to a shared network.
I want to know how to encode the public key and domain parameters, so that the verifier of the signature will decode it to use it.because when you send them over the network to the verifier u gonna have to encode the as a single byte array.i am no using Bouncy Castle Provider. The whole implementation of ECDSA algorithm is my project
Elliptic curve points are almost always encoded using the encoding specified in X9.62.
It is optional to use point compression. It is trivial to encode using point compression, but decoding a compressed point needs a bit more work, so unless you really need to save the extra bytes, I would not bother. Let me know if you need it, and I will add the details. You can recognize X9.62 encoded points with point compression by the first byte, which will be 0x02 or 0x03.
Encoding without point compression is really simple: start with a 0x04 (to indicate no compression). Then follow with first the x coordinate, then the y coordinate, both zero-padded on the left up to the size in bytes of the field:
int qLength = (q.bitLength()+7)/8;
byte[] xArr = toUnsignedByteArray(x);
byte[] yArr = toUnsignedByteArray(y);
byte[] res = new byte[1+2*qLength];
res[0] = 0x04;
System.arraycopy(xArr, 0, res, qLength - xArr.length, xArr.length);
System.arraycopy(yArr, 0, res, 2* qLength - yArr.length, nLength);
Decoding this is of course trivial.
I'm pretty sure that the BC implementation uses X9.63 encoding, so these would be rather standardized encodings. You will need to add the Bouncy Castle provider to your JRE (Security.addProvider(new BouncyCastleProvider()), see the bouncy documentation.
public static void showECKeyEncodings() {
try {
KeyPairGenerator kp = KeyPairGenerator.getInstance("ECDSA");
ECNamedCurveParameterSpec ecSpec = ECNamedCurveTable
.getParameterSpec("prime192v1");
kp.initialize(ecSpec);
KeyPair keyPair = kp.generateKeyPair();
PrivateKey privKey = keyPair.getPrivate();
byte[] encodedPrivKey = privKey.getEncoded();
System.out.println(toHex(encodedPrivKey));
PublicKey pubKey = keyPair.getPublic();
byte[] encodedPubKey = pubKey.getEncoded();
System.out.println(toHex(encodedPubKey));
KeyFactory kf = KeyFactory.getInstance("ECDSA");
PublicKey pubKey2 = kf.generatePublic(new X509EncodedKeySpec(encodedPubKey));
if (Arrays.equals(pubKey2.getEncoded(), encodedPubKey)) {
System.out.println("That worked for the public key");
}
PrivateKey privKey2 = kf.generatePrivate(new PKCS8EncodedKeySpec(encodedPrivKey));
if (Arrays.equals(privKey2.getEncoded(), encodedPrivKey)) {
System.out.println("That worked for the private key");
}
} catch (GeneralSecurityException e) {
throw new IllegalStateException(e);
}
}

Decrypt Rijndael 256 (from PhP) encoded text in java with little information

I have some data from a external party which is encrypted according to them in: 'Rijndeal 256 with the private key'
Alongside these records there are a public and private key certificate which look like RSA certificates.
From what i've learned so far it seems the common way to use encryption with certifcates is to generate a 'secret key' or some kind in initialization vector and use this to encrypted text. So i'm thinking this is probably what they have done (the data was encrypted by a PHP application)
I'm trying to decrypt this text with javax.crypto.Cipher but i think i problably need more information on the specific encryption, but i dont really know what information to ask for, and think its likely the 'default options' will probably work. (Communication with the supplying party is difficult and slow).
i'm currently Using the following code to get the private key:
InputStreamReader ir = new InputStreamReader(the_inputstream_for_the_private_key_record);
Security.addProvider(new BouncyCastleProvider());
pemr = new PEMReader(ir);
Object o = pemr.readObject();
keyPair kp = (KeyPair) o;
return kp.getPrivate();
This seems to work as i get a instantiated PrivateKey object without errors the toString looks like:
RSA Private CRT Key
modulus: c98faa50ba69<trimmed>
public exponent: 10001
private exponent: bb889fbe5cb2a6763f...<trimmed>
primeP: eb73e85dc636f5751b...<trimmed>
primeQ: db269bd603a2b81fc9...<trimmed>
primeExponentP: 85b9f111c190595cc8...<trimmed>
primeExponentQ: a66d59a75bb77530de...<trimmed>
crtCoefficient: 79415b078c4c229746...<trimmed>
For each record i also have a entry like the following:
{
"decryptedLength":128389,
"symKeyLength":32,
"symKey":"SImE8VnSZaAu1Ve...<trimmed (this is always 685 chars long) >...ayaJcnpSeOqAGM7q="
}
Basically this is where i'm a bit stuck.
My guess would be that that 'symkey' value is encrypted with RSA which in turn when decrypted would yield the secretKey for the AES part, but if i try:
Cipher rsaCipher = Cipher.getInstance("RSA");
rsaCipher.init(Cipher.DECRYPT_MODE, key);
byte[] b = rsaCipher.doFinal('symkey'.getbytes());
this gets me "javax.crypto.IllegalBlockSizeException: Data must not be longer than 512 bytes", which seems logical since this string is 685characters long
I'm probably missing something very obvious here...
Any suggestions are appreciated.
Just guessing, but I think the value
"symKey":"SImE8VnSZaAu1Ve...<trimmed (this is always 685 chars long) >...ayaJcnpSeOqAGM7q="
is the base64 encoded output from RSA encryption using a 4096-bit public key. You need to first base64 decode the value into a byte[] array, then decrypt it with the private key, the result of which will be a 256-bit key. Note that "Rijndael 256" is ambiguous, since Rijndael supports both a 256 bit blocksize and also a 256 bit keysize.
with GregS's answer i finaly got this to work.
(adding an answer in case someone else needs to decrypt similar php encoded stuff).
The first part was to decrypt de symmetricKey ("symkey") from the metaData string
This was as Greg notes a Base64 encoded, RSA encrypted key which was decoded like so:
Cipher rsaCipher = Cipher.getInstance("RSA");
rsaCipher.init(Cipher.DECRYPT_MODE, key);
byte[] encryptedRijndaelKey = Base64.decodeBase64(base64EncodedSymetricKey); //from the metaData
byte[] rijndaelKeyBytes = rsaCipher.doFinal(encryptedRijndaelKey);
This Rijndael key was then used to decrypt de actual encrypted data like so:
RijndaelEngine rijndaelEngine = new RijndaelEngine(256); // *1 *2
KeyParameter keyParam = new KeyParameter(rijndaelKeyBytes)
rijndaelEngine.init(false, keyParam); //false == decrypt
PaddedBufferedBlockCipher bbc = new PaddedBufferedBlockCipher(rijndaelEngine, new ZeroBytePadding()); // *3
byte[] decryptedBytes = new byte[decryptedLenght]; //from the storageOptions string
int processed = bbc.processBytes(inputBytes, 0, inputBytes.length, decryptedBytes, 0);
bbc.doFinal(decryptedBytes, processed);
*1 because the Sun JCA only supports common AES which has a 128bits keysize i had to use a different provider (BouncyCastle).
*2 apparently the blocksize was also 256 bits (trail & error)
*3 apparently there was no padding used, thus the ZeroPadding for padding (again trail & error).
The symKey value is Base64 encoded and must be decoded before you can do the private key decryption on it. Also, the symmetric encryption sounds like it is AES-256. (AES is based on the Rijndael cipher).

Categories