Public key generated on iOS device invalid on Java server - java

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.

Related

ascii string to hexadecimal byte array java

I have a java program where a read a identitynumber. Like "1234567" Now i want to convert that ascii value to a hexadecimal value and put that in a hexadecimal byte array like this :
byte[] bytes = {0x02, 0x0b, 0x39, 0x36, 0x31, 0x31, 0x32, 0x36, 0x31, 0x39, 0x39, 0x34, 0x37, 0x32, 0x03};
Problem is .. I have no idea how that i can do that .. Have you a solution or tips ? Thanks !
String foo = receivedData.substring(10,21);
BigInteger data = new BigInteger(foo);

Convert Java AES encryption to PHP

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'];

How to initialize a EC key pair with an existing key pair in JavaCard?

I already have a EC key pair(secp256r1) in a binary format which is stored in a byte array, like this:
// private key, 32 bytes
byte[] privKey = {0x4c, (byte)0xc7, (byte)0xcf, 0x68, (byte)0x91, 0x18, (byte)0x96, (byte)0xc8, (byte)0xe2, (byte)0xf9, (byte)0xc8, (byte)0xcc, 0x2f, 0x7f, 0x0a, (byte)0xa2, 0x1c, 0x6a, (byte)0xcb, (byte)0xba, 0x38, 0x1c, 0x10, (byte)0x9a, (byte)0xfe, (byte)0x91, 0x18, (byte)0xf6, (byte)0xca, (byte)0xd9, 0x0f, 0x0b};
//public key, 65 bytes, which is contained in a X.509 certificate
byte[] pubKey = {0x04, 0x72, (byte)0x9a, 0x71, (byte)0xd0, (byte)0x81, 0x62, 0x42, (byte)0x84, (byte)0x92, (byte)0xf2, (byte)0xd9, 0x61, (byte)0x92, 0x4d, 0x37, 0x44, 0x3a, 0x4f, 0x1b, (byte)0xda, 0x58, 0x0f, (byte)0x8a, (byte)0xea, 0x29, 0x20, (byte)0xd2, (byte)0x99, 0x7c, (byte)0xbe, (byte)0xa4, 0x39, 0x60, (byte)0xce, 0x72, (byte)0x9e, 0x35, (byte)0xc1, (byte)0xf7, 0x40, (byte)0x92, (byte)0xf2, 0x25, 0x0e, 0x60, 0x74, (byte)0x82, 0x3f, (byte)0xc5, 0x7f, 0x33, 0x60, (byte)0xb7, (byte)0xcd, 0x39, 0x69, (byte)0xc3, (byte)0xc3, 0x12, 0x5e, (byte)0xce, 0x26, 0x5c, 0x29};
This EC key pair is generated by openssl. I want to store this EC key pair in my javacard applet, so that I can signature message with this EC private key every time.
But I don't find any appropriate API in javacard 3 to set the EC key pair.
I use this project https://github.com/Yubico/ykneo-curves/blob/master/applet/src/com/yubico/ykneo/curves/SecP256r1.java to set the parameters in secp256r1.
UPDATE
I do have set the parameters setW in ECPublicKey and setS in ECPrivateKey, and other parameters according to https://github.com/Yubico/ykneo-curves/blob/master/applet/src/com/yubico/ykneo/curves/SecP256r1.java. Like this:
privKey.setFieldFP(p, (short) 0, (short) p.length);
privKey.setA(a, (short) 0, (short) a.length);
privKey.setB(b, (short) 0, (short) b.length);
privKey.setG(G, (short) 0, (short) G.length);
privKey.setR(r, (short) 0, (short) r.length);
byte[] privData = {(byte)0x25, (byte)0xc9, (byte)0xec, (byte)0xdc, (byte)0x4c, (byte)0x59, (byte)0xa3, (byte)0xe0, (byte)0x4f, (byte)0x01, (byte)0x56, (byte)0x97, (byte)0xf3, (byte)0xcb, (byte)0x60, (byte)0x5b, (byte)0x84, (byte)0x49, (byte)0x45, (byte)0x3a, (byte)0xe2, (byte)0x0e, (byte)0xd1, (byte)0xbd, (byte)0xc0, (byte)0xa7, (byte)0xe1, (byte)0xfa, (byte)0x82, (byte)0xee, (byte)0x3c, (byte)0x73};
privKey.setS(privData, (short) 0, (short) privData.length);
pubKey.setFieldFP(p, (short) 0, (short) p.length);
pubKey.setA(a, (short) 0, (short) a.length);
pubKey.setB(b, (short) 0, (short) b.length);
pubKey.setG(G, (short) 0, (short) G.length);
pubKey.setR(r, (short) 0, (short) r.length);
byte[] pubData = {0x04, 0x00, (byte)0xb9, (byte)0x8f, (byte)0xcf, (byte)0xc3, (byte)0xc0, (byte)0xae, (byte)0x95, 0x6a, 0x5b, 0x12, 0x6d, (byte)0xbe, 0x43, (byte)0xe4, 0x7f, 0x09, 0x0d, (byte)0xde, 0x02, (byte)0xd2, 0x6b, 0x28, (byte)0x86, (byte)0xed, 0x2b, (byte)0xd7, (byte)0xe2, (byte)0xc2, 0x69, (byte)0xc1, (byte)0x89, (byte)0xb2, 0x53, (byte)0x96, (byte)0xc1, 0x2d, (byte)0xbf, 0x4c, 0x30, (byte)0xae, (byte)0xd5, (byte)0xd5, 0x3c, (byte)0xb5, (byte)0xf9, 0x3b, 0x20, 0x37, (byte)0x83, (byte)0x88, (byte)0x9f, 0x34, 0x74, (byte)0xf5, 0x6c, (byte)0x97, 0x1e, 0x0a, (byte)0xa9, (byte)0xe7, (byte)0xfa, (byte)0xa6, 0x69};
pubKey.setW(pubData, (short) 0, (short)pubData.length);
Now I signature the message:
// the class Secp256r1 can be found in above link
pair = SecP256r1.newKeyPair();
ECPublicKey pubKey = (ECPublicKey) pair.getPublic();
ECPrivateKey privKey = (ECPrivateKey) pair.getPrivate();
byte[] data = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
signature = Signature.getInstance(Signature.ALG_ECDSA_SHA, false);
signature.init(pair.getPrivate(), Signature.MODE_SIGN);
byte[] signData = new byte[127];
short sendLen = signature.sign(data, (short) 0, (short) data.length, buffer, (short) 0);
apdu.setOutgoingAndSend((short) 0, sendLen);
I send several APDUs to call this code fragment. But every time I receive a differnt sign message. Why should this happened?
There are methods setW(...) on ECPublicKey and setS(...) on ECPrivateKey.
The tricky part is that your keyPair.getPublic() and keyPair.getPrivate() return general interfaces. You have to cast them:
KeyPair keyPair = new KeyPair(KeyPair.ALG_EC_FP, KeyBuilder.LENGTH_EC_FP_256);
ECPublicKey pub = (ECPublicKey) keyPair.getPublic();
ECPrivateKey priv = (ECPrivateKey) keyPair.getPrivate();
pub.setW(pubBytes, (short) 0, (short) pubBytes.length);
priv.setS(privBytes, (short) 0, (short) privBytes.length);
//do not forget to set parameters of your curve to both private and public key HERE!!!
https://docs.oracle.com/javacard/3.0.5/api/javacard/security/ECPublicKey.html
void setW(byte[] buffer,
short offset,
short length)
throws CryptoException
Sets the point of the curve comprising the public key. The point
should be specified as an octet string as per ANSI X9.62. A specific
implementation need not support the compressed form, but must support
the uncompressed form of the point. The plain text data format is
big-endian and right-aligned (the least significant bit is the least
significant bit of last byte). Input parameter data is copied into the
internal representation.
https://docs.oracle.com/javacard/3.0.5/api/javacard/security/ECPrivateKey.html
void setS(byte[] buffer,
short offset,
short length)
throws CryptoException
Sets the value of the secret key. The plain text data format is
big-endian and right-aligned (the least significant bit is the least
significant bit of last byte). Input parameter data is copied into the
internal representation.
ANSWER to UPDATE:
Signatures based on an elliptic curve contain a random number, that is why you get a different result each time you compute a signature. It is a feature, not a bug.

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