Convert Java AES encryption to PHP - java

I have a requirement to use AES encryption with specific parameters but the only example provided is in Java. I need to move everything to PHP and I'm not sure how to do it exactly.
In Java the encryption class takes the iv/salt parameters as an array of bytes directly. Something in the likes of:
byte[] iv = {(byte) 0xCB, (byte) 0x35, (byte) 0xF3, (byte) 0x52, (byte) 0x1A, (byte) 0xF7, (byte) 0x38, (byte) 0x0B, (byte) 0x75, (byte) 0x03, (byte) 0x8E, (byte) 0xE0, (byte) 0xEF, (byte) 0x39, (byte) 0x98, (byte) 0xC7};
AlgorithmParameterSpec params = new IvParameterSpec(iv);
but PHP expects a string for input, so I tried to do something like:
private $salt = ['a7', '70', '1f', 'f6', '5e', 'd3', '29', '8f'];
private $iv = ['cb', '35', 'f1', '52', '1b', 'f7', '33', '0b', '75', '03', '8e', 'e0', 'cf', '39', '98', 'c7'];
public function __construct()
{
$iv = implode(array_map("hex2bin", $this->iv));
$this->iv = $iv;
$salt = implode(array_map("hex2bin", $this->salt));
$this->salt = $salt;
}
public function encrypt($unencryptedString)
{
$key = hash_pbkdf2('sha1', $this->passPhrase, $this->salt, $this->iterationCount, $this->keyLen, true);
var_dump($key);
$hash = openssl_encrypt($unencryptedString, 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $this->iv);
$encoded = base64_encode($hash);
return $encoded;
}
I imagine I'm not using the iv/salt parameters the same way its used on Java, thats why it doesn't produce the same thing. Suggestions?

Basics (You did good!)
You should always Use a standard library for PHP encryption.
I know we shouldn't be making recommendations but encryption is in my opinion somewhat of an exception and you should utilise tried and tested libraries.
Use either the openssl extensions or libsodium
Answers on using both are linked here.
How to encrypt plaintext with AES-256 CBC in PHP using openssl()?
How to encrypt / decrypt AES with Libsodium-PHP
Problems?
In terms of your implemtation I think the $iv and $key aren't well formed inputs for hex2bin()
$iv = ['0xA7', '0x71', '0x1F', '0xF5', '0x5D', '0xD2', '0x28', '0x8F'];
array_map("hex2bin", $iv);
// outputs Warning: hex2bin(): Input string must be hexadecimal string

Following #Luke's suggestion:
Removing 0x and using lower case in all iv/salt values seems to do it.
private $salt = ['a7', '71', '1f', 'f5', '5d', 'd2', '28', '8f'];

Related

Twofish encryption decryption Algorithm

I am using a sample code from a git repository to understand twofish algorithm, The code below works very fine the results are also correct checked from an online tool ref http://twofish.online-domain-tools.com/
the problem is as below :-
int[] plainText = new int[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C,
0x0D, 0x0E, 0x0F };
int[] p = new int[4];
for (int i = 0; i < 4; i++) {
p[i] = plainText[4 * i] + 256 * plainText[4 * i + 1] + (plainText[4 * i + 2] << 16)
+ (plainText[4 * i + 3] << 24);
}
System.out.println("Input:");
Utils.printInput(p);
int[] key = p; //
System.out.println("Key:");
Utils.printInput(key);
//
int[] encrypted = TwoFish.encrypt(p, key);
//
System.out.println("Encrypted:");
Utils.printInput(encrypted);
System.out.println();
int[] decrypted = TwoFish.decrypt(encrypted, key);
System.out.println("Decrypted:");
Utils.printInput(decrypted);
In the code above same key is used as plainText and Key, While there is a requirement of passing plain text and key
int[] encrypted = TwoFish.encrypt(p, key);
above code needs to take input from
int[] encrypted = TwoFish.encrypt("The plain text information", "000102030405060708090A0B0C0D0E0F");
OK, cryptography primer:
You need a mode of operation for the Twofish block cipher. I have trouble to recognize one you have in the code though, and that's not a good sign.
The mode of operation needs an IV, and a random - or at least a fully unpredictable IV - for CBC mode.
Your plaintext you need to encode. Using UTF-8 is recommended nowadays (it's compatible with ASCII, so for your string you really cannot go wrong).
You need a hexadecimal decoder to decode the key to a byte array.
By the way, generally we implement cryptographic block ciphers and other primitives to operate on bits - or more specifically bytes. The cipher or at least the mode of operation should accept bytes, not integers.
Good luck!

Public key generated on iOS device invalid on Java server

I have an issue with a SecKeyRef generated on iOS device - when trying to use it on Java server, the exception is thrown:
InvalidKeyException: EC domain parameters must be encoded in the algorithm identifier
here's the code snippet from the server code:
String key = ...
byte[] byteKey = Base64.decode(key.getBytes(StandardCharsets.UTF_8));
X509EncodedKeySpec X509publicKey = new X509EncodedKeySpec(byteKey);
KeyFactory kf = KeyFactory.getInstance("EC");
return kf.generatePublic(X509publicKey);
The exception is thrown by kf.generatePublic(X509publicKey);
The key is created on iOS, using SecKeyGeneratePair
[keyPairAttr setObject:(__bridge id)kSecAttrKeyTypeEC forKey:(__bridge id)kSecAttrKeyType];
[keyPairAttr setObject:[NSNumber numberWithUnsignedInteger:256] forKey:(__bridge id)kSecAttrKeySizeInBits];
// Set the private key dictionary
[privateKeyAttr setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecAttrIsPermanent];
[privateKeyAttr setObject:self.privateTag forKey:(__bridge id)kSecAttrApplicationTag];
// Set the public key dictionary
[publicKeyAttr setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecAttrIsPermanent];
[publicKeyAttr setObject:self.publicTag forKey:(__bridge id)kSecAttrApplicationTag];
// Set attributes to top level dictionary
[keyPairAttr setObject:privateKeyAttr forKey:(__bridge id)kSecPrivateKeyAttrs];
[keyPairAttr setObject:publicKeyAttr forKey:(__bridge id)kSecPublicKeyAttrs];
// Generate key pair
OSStatus sanityCheck = SecKeyGeneratePair((__bridge CFDictionaryRef)keyPairAttr, &publicKeyRef, &privateKeyRef);
The key pair is created successfully. I then extract key's bit data using following code
CFDataRef publicKeyBitsRef = NULL;
NSMutableDictionary *queryPublicKey = [NSMutableDictionary dictionary];
// Set the public key query dictionary.
[queryPublicKey setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass];
[queryPublicKey setObject:self.publicTag forKey:(__bridge id)kSecAttrApplicationTag];
[queryPublicKey setObject:(__bridge id)kSecAttrKeyTypeEC forKey:(__bridge id)kSecAttrKeyType];
[queryPublicKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecReturnData];
// Get the key bits.
OSStatus sanityCheck = SecItemCopyMatching((__bridge CFDictionaryRef)queryPublicKey, (CFTypeRef *)&publicKeyBitsRef);
Then, I export the key using CryptoExportImportManager
NSData *publicKeyIDERData = [manager exportPublicKeyToDER:keyBits keyType:(__bridge NSString*)kSecAttrKeyTypeEC keySize:256];
NSString *derKeyString = [publicKeyIDERData base64EncodedStringWithOptions:0];
According to this answer, the DER header contains info about key type and parameters and for secp256r1 key it's equivalent of following data
[
0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86,
0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a,
0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03,
0x42, 0x00
]
Which is indeed added to key header on export.
derKeyString is then sent to backend and processed using Java code mentioned above. However, the exception is thrown.
The same backend processes also the key created on Android device using following code
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
keyPairGenerator.initialize(new KeyGenParameterSpec.Builder(KEY_NAME, KeyProperties.PURPOSE_SIGN)
.setDigests(KeyProperties.DIGEST_SHA256)
.setAlgorithmParameterSpec(
new ECGenParameterSpec("secp256r1"))
.setUserAuthenticationRequired(true).build());
keyPairGenerator.generateKeyPair();
The Android key works just fine.
What am I doing wrong? Have I forgotten about something while creating keys with SecKeyGeneratePair or exporting the public key?
I solved the problem, although I'm not sure why.
What I did is I ditched CryptoExportImportManager library and I'm manually creating key data, like so:
unsigned char _encodedECOID[] = {
0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86,
0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a,
0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03,
0x42, 0x00
};
NSMutableData *data = [NSMutableData new];
[data appendBytes:_encodedECOID length:sizeof(_encodedECOID)];
[data appendData:keyBits]; // keyBits is od NSData type
Now the Java server properly creates public key from my string (base64 encoded from data).
However, having looked at source code of CryptoExportImportManager, the way it creates the encoded string from my key bits looks like that (in Swift):
let curveOIDHeader: [UInt8] = [0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86,
0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x08, 0x2A,
0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x03,
0x42, 0x00]
let curveOIDHeaderLen: Int = 26
var data = Data(bytes: curveOIDHeader, count: curveOIDHeaderLen)
data.append(rawPublicKeyBytes)
It basically does exactly the same thing. So where's the difference?
Now the only thing that comes to mind is the difference in how the header is stored - in my case it's an array of unsigned char, in library's case it's an array of UInt8.
According to this answer, C types unsigned char and uint8_t are not equivalent, they are only guaranteed to have the same length, but can differ in i.e. byte ordering.
Though that question had nothing to do with Swift's UInt8 (but was tagged C, of which Objective-C is superset), the documentation of Swift's UInt8 type says nothing about it's relation to unsigned char type, that would be the only reasonable explanation I can see.

How to use Java Card crypto sample?

I'm trying to make run example from IBM website.
I wrote this method:
public static byte[] cipher(byte[] inputData) {
Cipher cipher
= Cipher.getInstance(
Cipher.ALG_DES_CBC_NOPAD, true);
DESKey desKey = (DESKey) KeyBuilder.buildKey(
KeyBuilder.TYPE_DES,
KeyBuilder.LENGTH_DES,
false);
byte[] keyBytes = {(byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04};
desKey.setKey(keyBytes, (short) 0);
cipher.init(desKey, Cipher.MODE_ENCRYPT);
byte[] outputData = new byte[8];
cipher.doFinal(inputData, (short) 0, (short) inputData.length, outputData, (short) 0);
return outputData;
}
And call this method cipher("test".getBytes());. When I call this servlet server gives me Internal server error and javacard.security.CryptoException.
I tried ALG_DES_CBC_ISO9797_M1, ALG_DES_CBC_ISO9797_M2 (and others) and got the same exception.
How to make run simple example of cipher on Java Card Connected?
UPDATE
As #vojta said, key must be 8 bytes long. So it must be something like this:
byte[] keyBytes = {(byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04};
I don't know why, but it works only if replace
Cipher cipher = Cipher.getInstance(Cipher.ALG_DES_CBC_NOPAD, true);
with
Cipher cipher = Cipher.getInstance(Cipher.ALG_DES_CBC_ISO9797_M2, false);
I could not find anything about it in documentation.
These lines seem to be wrong:
byte[] keyBytes = {(byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04};
desKey.setKey(keyBytes, (short) 0);
DES key should be longer than 4 bytes, right? Standard DES key is 8 bytes long (with strength of 56 bits).
In addition to #vojta's answer, the input data should be block aligned.
Your input data "test".getBytes() have length 4 which is not valid for Cipher.ALG_DES_CBC_NOPAD (but valid for Cipher.ALG_DES_CBC_ISO9797_M2).
Strange is that this should cause CryptoException.ILLEGAL_USE reason (which is 5 opposed to 3 you are getting)...

how to build a rsa key with a modulus of 64 bytes in java

I have a java card applet which will generate a RSA private public key pair (512 bits each). And it will send the public key modulus and exponent (modulus is 64 bytes)
In the host application (java) i need to re build the rsa public key using the same exponent and modulus, but when i try to reconstruct using the following code i am getting an error.
Java card code:
// this one to create the key pair
rsa_KeyPair = new KeyPair(KeyPair.ALG_RSA_CRT, KeyBuilder.LENGTH_RSA_512);
rsa_KeyPair.genKeyPair();
rsa_PublicKey = (RSAPublicKey) rsa_KeyPair.getPublic();
rsa_PrivateCrtKey 0= (RSAPrivateCrtKey) rsa_KeyPair.getPrivate();
cipherRSA = Cipher.getInstance(Cipher.ALG_RSA_PKCS1, false);
//this is to send the modulus
byte[] buffer = apdu.getBuffer();
rsa_PublicKey.getModulus(buffer, ISO7816.OFFSET_CDATA);
apdu.setOutgoing();
apdu.setOutgoingLength((short) 64);
apdu.sendBytesLong(buffer, ISO7816.OFFSET_CDATA, (short) 64);
This part of the code is working fine. i am able to send the modulus perfectly to the host side.
Java code for host application below:
//command for retrieving modulus
resp = channel.transmit(new CommandAPDU(cmdMod));
BigInteger modulus = new BigInteger(resp.getData());
I am getting the 64 byte modulus as expected but when i make a big integer out of it its showing a large negative value.
//command for retrieving exponent
resp = channel.transmit(new CommandAPDU(cmdExp));
BigInteger modulus = new BigInteger(resp.getData());
byte[] input = { (byte) 0x92, (byte) 0x84, (byte) 0x3B,
(byte) 0xD3, (byte) 0x5D, (byte) 0x8A, (byte) 0x6B,
(byte) 0x56, (byte) 0xDA, (byte) 0xEA, (byte) 0xE0,
(byte) 0x2F, (byte) 0x6D, (byte) 0xAA, (byte) 0x62,
(byte) 0x4B, (byte) 0x38, (byte) 0xCE, (byte) 0xD4,
(byte) 0x70, (byte) 0xA2, (byte) 0x16, (byte) 0x35,
(byte) 0xCC, (byte) 0xEE, (byte) 0xB8, (byte) 0x31,
(byte) 0x13, (byte) 0x37, (byte) 0x40, (byte) 0xBE,
(byte) 0xA1, (byte) 0xCD, (byte) 0x84, (byte) 0xD9,
(byte) 0xF3, (byte) 0xE6, (byte) 0xCE, (byte) 0x26,
(byte) 0x0A, (byte) 0xC1, (byte) 0x40, (byte) 0xED,
(byte) 0x20, (byte) 0x8F, (byte) 0x3D, (byte) 0x9F,
(byte) 0x0D, (byte) 0xE7, (byte) 0x19, (byte) 0xC8,
(byte) 0x87, (byte) 0x96, (byte) 0x29, (byte) 0xF2,
(byte) 0x63, (byte) 0x34, (byte) 0x6D, (byte) 0x10,
(byte) 0xB9, (byte) 0xFB, (byte) 0xB4, (byte) 0x75,
(byte) 0xE9 };
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA")
.generatePublic(new RSAPublicKeySpec(modulus, exponent));
Cipher cipher = null;
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, pubKey);
byte[] cipherText = cipher.doFinal(input);
Error:
javax.crypto.BadPaddingException: Message is larger than modulus
at sun.security.rsa.RSACore.parseMsg(Unknown Source)
at sun.security.rsa.RSACore.crypt(Unknown Source)
at sun.security.rsa.RSACore.rsa(Unknown Source)
at com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:355)
at com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:389)
at javax.crypto.Cipher.doFinal(Cipher.java:2121)
at testAuth.main(testAuth.java:150)
I checked the reponse from the card. i am getting all the 64 bytes of the modulus correctly. but when i make the Big Integer i am getting a large negative value. what should i do?
The issue is that BigInteger by default encodes to a signed big endian representation. If you decode the bytes using the constructor it does the opposite, i.e. it expects a signed value. Now most cryptography is performed on (large) unsigned integers. This is because the calculations are performed within a mathematical group (modulus calculations). These calculations are always performed on positive numbers, and RSA is no exception to this rule.
Now the size of the modulus is equal to the key size (not the key strength) of the RSA key. This means that an RSA key of 512 bit has a modulus of exactly 512 bits when encoded as a unsiged, big endian number. For numbers this means that the most significant bit is always set to '1'. However, that bit is used to indicate the sign bit for unsigned numbers encoded as a two-complement value. In other words, any modulus that has a key size dividable by 8 will be negative when interpreted as signed value.
The solution is of course to use the constructor where you can indicate the sign bit yourself:
BigInteger(int signum, byte[] magnitude)
where magnitude is the unsigned representation, in your case:
new BigInteger(1, resp.getData());
Java Card uses unsigned representations in the API, as it is more cryptographically oriented. No need for complicated methods in there.
Note that the reverse - creating a statically sized byte array out of an encoded signed BigInteger is even trickier, see this answer for information about how to perform that particular conversion.

3DES/DES encryption using the JCE - generating an acceptable key

I'm working on a project that requires 3DES encryption in Java. The issue is that I've been (and will continue to be) supplied with a 128-bit hex key like "0123456789ABCDEF0123456789ABCDEF". Conversion to bytes is no issue. What is the issue, however, is that the Java Cryptographic Extensions API will choke on this key, saying it is invalid. I gather that the MSB of each byte is merely a parity bit, so the JCE expects me to remove those (or so I think). In .NET, however, I can specify the key as supplied, and it quietly handles the encryption/decryption with no complaints.
Is there any way I can generate the kind of key the JCE expects from the kind of key I'm supplied?
I've found that the JCE allows you specify an 8-byte key for DES encryption, so I tried implementing 3DES as DES EDE using half of the supplied key. However, I'm still getting inconsistent results with .NET.
Here's the Java code:
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
public class Main{
public static void main(String[] args) throws Exception {
byte [] plain = "I eat fish every day".getBytes("utf-8");
byte [] keyBytes = new byte [] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00
};
byte [] key2Bytes = new byte [] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x0 }; // actual keys replaced with dummies.
SecretKey keySpec = new SecretKeySpec(keyBytes, "DES");
SecretKey keySpec2 = new SecretKeySpec(key2Bytes, "DES");
IvParameterSpec iv = new IvParameterSpec(new byte[8]);
Cipher e_cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
Cipher cipher = Cipher.getInstance("DES/CBC/NoPadding");
e_cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
cipher.init(Cipher.DECRYPT_MODE, keySpec2, iv);
byte [] cipherText = e_cipher.doFinal(plain);
cipherText = cipher.doFinal(cipherText);
cipherText = e_cipher.doFinal(cipherText);
System.out.println("Ciphertext: " + new sun.misc.BASE64Encoder().encode(cipherText));
}
}
and here's the .NET code:
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace EncryptionDemo
{
class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Hello World!");
// TODO: Implement Functionality Here
var plainBytes = Encoding.UTF8.GetBytes("I eat fish every day");
var keyBytes = new byte [] { 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00 };
var tripleDES = TripleDESCryptoServiceProvider.Create();
var transform = tripleDES.CreateEncryptor(keyBytes, new byte [8]);
var memStream = new MemoryStream();
var cStream = new CryptoStream(memStream, transform, CryptoStreamMode.Write);
cStream.Write(plainBytes, 0, plainBytes.Length);
cStream.FlushFinalBlock();
//memStream.Position = 0;
var cipherBytes = memStream.ToArray();
Console.WriteLine("Ciphertext: " + Convert.ToBase64String(cipherBytes));
Console.Write("Press any key to continue . . . ");
Console.ReadKey(true);
}
}
Both produce different outputs (some characters in the Base64 string are the same)
3DES keys are 192 bits long.
How are you creating the SecretKey instance? What error message to you get?
The Java code in your question is using DES, not "Triple DES". The algorithm name should be "DESede/CBC/PKCS5Padding". The code in your answer probably works because you got the algorithm right, not because you switched providers. The SunJCE provider in Java 6 will accept 128-bit keys (and use keying option 2). I am not sure about older versions.
the Sun provider doesn't accept 16-byte 3DES keys, but the BouncyCastle provider does. I just tried it out and it works like a charm - it produces the same output as the .NET code!
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
public class Main{
public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastleProvider());
byte [] plain = "I eat fish every day".getBytes("utf-8");
byte [] keyBytes = new byte [] { (byte) 0xC1, (byte) 0x57, (byte) 0x45, (byte) 0x08,
(byte) 0x85, (byte) 0x02, (byte) 0xB0, (byte) 0xD3,
(byte) 0xA2, (byte) 0xEF, (byte) 0x68, (byte) 0x43,
(byte) 0x5E, (byte) 0xE6, (byte) 0xD0, (byte) 0x75 };
SecretKey keySpec = new SecretKeySpec(keyBytes, "DESede");
IvParameterSpec iv = new IvParameterSpec(new byte[8]);
Cipher e_cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding", "BC");
e_cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
byte [] cipherText = e_cipher.doFinal(plain);
System.out.println("Ciphertext: " + new sun.misc.BASE64Encoder().encode(cipherText));
}
}
In the jPOS project, the problem is worked around by always using either single-length (8-byte) or triple-length (24-byte) keys. Let's say your clear double-length key (in bytes) is AAAAAAAA BBBBBBBB. All code in the jPOS project I've seen so far that uses the JCE appends the first 8 bytes again to the clear key, so it becomes a triple-length key as such: AAAAAAAA BBBBBBBB AAAAAAAA. It seems the Sun provider does accept this material for creating a SecreKeySpec, as it is 192 bits long, as #erickson mentioned.

Categories

Resources