TripleDES encryption error in java - java

I'm following this tutorial to use 3DES encryption, and i needed to make some changes in cipher settings, so here's my code:
public class TripleDES {
public static int MAX_KEY_LENGTH = DESedeKeySpec.DES_EDE_KEY_LEN;
private static String ENCRYPTION_KEY_TYPE = "DESede";
private static String ENCRYPTION_ALGORITHM = "DESede/ECB/PKCS7Padding";
private final SecretKeySpec keySpec;
private final static String LOG = "TripleDES";
public TripleDES(String passphrase) {
byte[] key;
try {
// get bytes representation of the password
key = passphrase.getBytes("UTF8");
} catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException(e);
}
key = padKeyToLength(key, MAX_KEY_LENGTH);
key = addParity(key);
keySpec = new SecretKeySpec(key, ENCRYPTION_KEY_TYPE);
}
// !!! - see post below
private byte[] padKeyToLength(byte[] key, int len) {
byte[] newKey = new byte[len];
System.arraycopy(key, 0, newKey, 0, Math.min(key.length, len));
return newKey;
}
// standard stuff
public byte[] encrypt(String message) throws GeneralSecurityException, UnsupportedEncodingException {
byte[] unencrypted = message.getBytes("UTF8");
return doCipher(unencrypted, Cipher.ENCRYPT_MODE);
}
public byte[] decrypt(byte[] encrypted) throws GeneralSecurityException {
return doCipher(encrypted, Cipher.DECRYPT_MODE);
}
private byte[] doCipher(byte[] original, int mode)
throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance(ENCRYPTION_ALGORITHM);
// IV = 0 is yet another issue, we'll ignore it here
// IvParameterSpec iv = new IvParameterSpec(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 });
cipher.init(mode, keySpec); //, iv);
return cipher.doFinal(original);
}
// Takes a 7-byte quantity and returns a valid 8-byte DES key.
// The input and output bytes are big-endian, where the most significant
// byte is in element 0.
public static byte[] addParity(byte[] in) {
byte[] result = new byte[8];
// Keeps track of the bit position in the result
int resultIx = 1;
// Used to keep track of the number of 1 bits in each 7-bit chunk
int bitCount = 0;
// Process each of the 56 bits
for (int i = 0; i < 56; i++) {
// Get the bit at bit position i
boolean bit = (in[6 - i / 8] & (1 << (i % 8))) > 0;
// If set, set the corresponding bit in the result
if (bit) {
result[7 - resultIx / 8] |= (1 << (resultIx % 8)) & 0xFF;
bitCount++;
}
// Set the parity bit after every 7 bits
if ((i + 1) % 7 == 0) {
if (bitCount % 2 == 0) {
// Set low-order bit (parity bit) if bit count is even
result[7 - resultIx / 8] |= 1;
}
resultIx++;
bitCount = 0;
}
resultIx++;
}
Log.d(LOG, "result: " + result);
return result;
}
}
But i'm getting InvalidKeyException on this line:
cipher.init(mode, keySpec);
LogCat:
W/System.err(758): java.security.InvalidKeyException: src.length=8 srcPos=8 dst.length=8 dstPos=0 length=8
W/System.err(758): at org.bouncycastle.jce.provider.JCEBlockCipher.engineInit(JCEBlockCipher.java:584)
W/System.err(758): at org.bouncycastle.jce.provider.JCEBlockCipher.engineInit(JCEBlockCipher.java:631)
W/System.err(758): at javax.crypto.Cipher.init(Cipher.java:511)
W/System.err(758): at javax.crypto.Cipher.init(Cipher.java:471)
I'm new on encrytion so i probably overlook something but i cannot find what it is. Any help is appreciated...

Triple DES requires a 24 byte key, not an 8 byte one.

I've found solution by changing these lines:
try {
// get bytes representation of the password
key = passphrase.getBytes("UTF8");
} catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException(e);
}
key = padKeyToLength(key, MAX_KEY_LENGTH);
key = addParity(key);
keySpec = new SecretKeySpec(key, ENCRYPTION_KEY_TYPE);
into these:
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
byte[] keyBytes = GetKeyAsBytes(key);
keySpec = new SecretKeySpec(keyBytes, "DESede");
while GetKeyAsBytes method is this:
public byte[] GetKeyAsBytes(String key) {
byte[] keyBytes = new byte[24]; // a Triple DES key is a byte[24] array
for (int i = 0; i < key.length() && i < keyBytes.length; i++)
keyBytes[i] = (byte) key.charAt(i);
return keyBytes;
}

Related

Decrypt an encrypted private key file with passphrase - java [duplicate]

I have an encrypted private key and I know the password.
I need to decrypt it using a Java library.
I'd prefer not to use BouncyCastle though, unless there is no other option. Based on previous experience, there is too much change and not enough documentation.
The private key is in this form:
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,56F3A98D9CFFA77A
X5h7SUDStF1tL16lRM+AfZb1UBDQ0D1YbQ6vmIlXiK....
.....
/KK5CZmIGw==
-----END RSA PRIVATE KEY-----
I believe the key data is Base64 encoded since I see \r\n after 64 characters.
I tried the following to decrypt the key:
import java.security.Key;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
public String decrypt(String keyDataStr, String passwordStr){
// This key data start from "X5... to =="
char [] password=passwordStr.toCharArray();
byte [] keyDataBytes=com.sun.jersey.core.util.Base64.decode(keyDataStr);
PBEKeySpec pbeSpec = new PBEKeySpec(password);
EncryptedPrivateKeyInfo pkinfo = new EncryptedPrivateKeyInfo(keyDataBytes);
SecretKeyFactory skf = SecretKeyFactory.getInstance(pkinfo.getAlgName());
Key secret = skf.generateSecret(pbeSpec);
PKCS8EncodedKeySpec keySpec = pkinfo.getKeySpec(secret);
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey pk=kf.generatePrivate(keySpec);
return pk.toString();
}
I get this Exception
java.io.IOException: DerInputStream.getLength(): lengthTag=50, too big.
at sun.security.util.DerInputStream.getLength(DerInputStream.java:561)
at sun.security.util.DerValue.init(DerValue.java:365)
at sun.security.util.DerValue.<init>(DerValue.java:294)
at javax.crypto.EncryptedPrivateKeyInfo.<init> (EncryptedPrivateKeyInfo.java:84)
Am I passing the right parameter to EncryptedPrivateKeyInfo constructor?
How can I make this work?
I tried what Ericsonn suggested, with one small change since I am working Java 7, I could not use Base64.getMimeCoder() instead I used Base64.decode and I am getting this error
I am getting an error like this Input length must be multiple of 8 when decrypting with padded cipher at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:750)
static RSAPrivateKey decrypt(String keyDataStr, String ivHex, String password)
throws GeneralSecurityException, UnsupportedEncodingException
{
byte[] pw = password.getBytes(StandardCharsets.UTF_8);
byte[] iv = h2b(ivHex);
SecretKey secret = opensslKDF(pw, iv);
Cipher cipher = Cipher.getInstance("DESede/CBC/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
byte [] keyBytes=Base64.decode(keyDataStr.getBytes("UTF-8"));
byte[] pkcs1 = cipher.doFinal(keyBytes);
/* See note for definition of "decodeRSAPrivatePKCS1" */
RSAPrivateCrtKeySpec spec = decodeRSAPrivatePKCS1(pkcs1);
KeyFactory rsa = KeyFactory.getInstance("RSA");
return (RSAPrivateKey) rsa.generatePrivate(spec);
}
private static SecretKey opensslKDF(byte[] pw, byte[] iv)
throws NoSuchAlgorithmException
{
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(pw);
md5.update(iv);
byte[] d0 = md5.digest();
md5.update(d0);
md5.update(pw);
md5.update(iv);
byte[] d1 = md5.digest();
byte[] key = new byte[24];
System.arraycopy(d0, 0, key, 0, 16);
System.arraycopy(d1, 0, key, 16, 8);
return new SecretKeySpec(key, "DESede");
}
private static byte[] h2b(CharSequence s)
{
int len = s.length();
byte[] b = new byte[len / 2];
for (int src = 0, dst = 0; src < len; ++dst) {
int hi = Character.digit(s.charAt(src++), 16);
int lo = Character.digit(s.charAt(src++), 16);
b[dst] = (byte) (hi << 4 | lo);
}
return b;
}
static RSAPrivateCrtKeySpec decodeRSAPrivatePKCS1(byte[] encoded)
{
ByteBuffer input = ByteBuffer.wrap(encoded);
if (der(input, 0x30) != input.remaining())
throw new IllegalArgumentException("Excess data");
if (!BigInteger.ZERO.equals(derint(input)))
throw new IllegalArgumentException("Unsupported version");
BigInteger n = derint(input);
BigInteger e = derint(input);
BigInteger d = derint(input);
BigInteger p = derint(input);
BigInteger q = derint(input);
BigInteger ep = derint(input);
BigInteger eq = derint(input);
BigInteger c = derint(input);
return new RSAPrivateCrtKeySpec(n, e, d, p, q, ep, eq, c);
}
private static BigInteger derint(ByteBuffer input)
{
byte[] value = new byte[der(input, 0x02)];
input.get(value);
return new BigInteger(+1, value);
}
private static int der(ByteBuffer input, int exp)
{
int tag = input.get() & 0xFF;
if (tag != exp)
throw new IllegalArgumentException("Unexpected tag");
int n = input.get() & 0xFF;
if (n < 128)
return n;
n &= 0x7F;
if ((n < 1) || (n > 2))
throw new IllegalArgumentException("Invalid length");
int len = 0;
while (n-- > 0) {
len <<= 8;
len |= input.get() & 0xFF;
}
return len;
}
1640 is keyDataStr.length() and 1228 is keyBytes.length
You need to use a non-standard, OpenSSL method for deriving the decryption key. Then use that to decrypt the PKCS-#1–encoded key—what you are working with is not a PKCS #8 envelope. You'll also need the IV from the header as input to these processes.
It looks something like this:
static RSAPrivateKey decrypt(String keyDataStr, String ivHex, String password)
throws GeneralSecurityException
{
byte[] pw = password.getBytes(StandardCharsets.UTF_8);
byte[] iv = h2b(ivHex);
SecretKey secret = opensslKDF(pw, iv);
Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
byte[] pkcs1 = cipher.doFinal(Base64.getMimeDecoder().decode(keyDataStr));
/* See note for definition of "decodeRSAPrivatePKCS1" */
RSAPrivateCrtKeySpec spec = decodeRSAPrivatePKCS1(pkcs1);
KeyFactory rsa = KeyFactory.getInstance("RSA");
return (RSAPrivateKey) rsa.generatePrivate(spec);
}
private static SecretKey opensslKDF(byte[] pw, byte[] iv)
throws NoSuchAlgorithmException
{
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(pw);
md5.update(iv);
byte[] d0 = md5.digest();
md5.update(d0);
md5.update(pw);
md5.update(iv);
byte[] d1 = md5.digest();
byte[] key = new byte[24];
System.arraycopy(d0, 0, key, 0, 16);
System.arraycopy(d1, 0, key, 16, 8);
return new SecretKeySpec(key, "DESede");
}
private static byte[] h2b(CharSequence s)
{
int len = s.length();
byte[] b = new byte[len / 2];
for (int src = 0, dst = 0; src < len; ++dst) {
int hi = Character.digit(s.charAt(src++), 16);
int lo = Character.digit(s.charAt(src++), 16);
b[dst] = (byte) (hi << 4 | lo);
}
return b;
}
This is already a lot of code, so I will link to another answer for the definition of the decodeRSAPrivatePKCS1() method.
Java code example below shows how to construct the decryption key to obtain the underlying RSA key from an encrypted private key created using the openssl 1.0.x genrsa command; specifically from the following genrsa options that may have been leveraged:
-des encrypt the generated key with DES in cbc mode
-des3 encrypt the generated key with DES in ede cbc mode (168 bit key)
-aes128, -aes192, -aes256 encrypt PEM output with cbc aes
Above options result in encrypted RSA private key of the form ...
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AAA,BBB
...
Where AAA would be one of:
DES-CBC, DES-EDE3-CBC, AES-128-CBC, AES-192-CBC, AES-256-CBC
AND BBB is the hex-encoded IV value
KeyFactory factory = KeyFactory.getInstance("RSA");
KeySpec keySpec = null;
RSAPrivateKey privateKey = null;
Matcher matcher = OPENSSL_ENCRYPTED_RSA_PRIVATEKEY_PATTERN.matcher(pemContents);
if (matcher.matches())
{
String encryptionDetails = matcher.group(1).trim(); // e.g. AES-256-CBC,XXXXXXX
String encryptedKey = matcher.group(2).replaceAll("\\s", ""); // remove tabs / spaces / newlines / carriage return etc
System.out.println("PEM appears to be OpenSSL Encrypted RSA Private Key; Encryption details : "
+ encryptionDetails + "; Key : " + encryptedKey);
byte[] encryptedBinaryKey = java.util.Base64.getDecoder().decode(encryptedKey);
String[] encryptionDetailsParts = encryptionDetails.split(",");
if (encryptionDetailsParts.length == 2)
{
String encryptionAlgorithm = encryptionDetailsParts[0];
String encryptedAlgorithmParams = encryptionDetailsParts[1]; // i.e. the initialization vector in hex
byte[] pw = new String(password).getBytes(StandardCharsets.UTF_8);
byte[] iv = fromHex(encryptedAlgorithmParams);
MessageDigest digest = MessageDigest.getInstance("MD5");
// we need to come up with the encryption key
// first round digest based on password and first 8-bytes of IV ..
digest.update(pw);
digest.update(iv, 0, 8);
byte[] round1Digest = digest.digest(); // The digest is reset after this call is made.
// second round digest based on first round digest, password, and first 8-bytes of IV ...
digest.update(round1Digest);
digest.update(pw);
digest.update(iv, 0, 8);
byte[] round2Digest = digest.digest();
Cipher cipher = null;
SecretKey secretKey = null;
byte[] key = null;
byte[] pkcs1 = null;
if ("AES-256-CBC".equals(encryptionAlgorithm))
{
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
key = new byte[32]; // 256 bit key (block size still 128-bit)
System.arraycopy(round1Digest, 0, key, 0, 16);
System.arraycopy(round2Digest, 0, key, 16, 16);
secretKey = new SecretKeySpec(key, "AES");
}
else if ("AES-192-CBC".equals(encryptionAlgorithm))
{
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
key = new byte[24]; // key size of 24 bytes
System.arraycopy(round1Digest, 0, key, 0, 16);
System.arraycopy(round2Digest, 0, key, 16, 8);
secretKey = new SecretKeySpec(key, "AES");
}
else if ("AES-128-CBC".equals(encryptionAlgorithm))
{
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
key = new byte[16]; // 128 bit key
System.arraycopy(round1Digest, 0, key, 0, 16);
secretKey = new SecretKeySpec(key, "AES");
}
else if ("DES-EDE3-CBC".equals(encryptionAlgorithm))
{
cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
key = new byte[24]; // key size of 24 bytes
System.arraycopy(round1Digest, 0, key, 0, 16);
System.arraycopy(round2Digest, 0, key, 16, 8);
secretKey = new SecretKeySpec(key, "DESede");
}
else if ("DES-CBC".equals(encryptionAlgorithm))
{
cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
key = new byte[8]; // key size of 8 bytes
System.arraycopy(round1Digest, 0, key, 0, 8);
secretKey = new SecretKeySpec(key, "DES");
}
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
pkcs1 = cipher.doFinal(encryptedBinaryKey);
keySpec = pkcs1ParsePrivateKey(pkcs1);
privateKey = (RSAPrivateKey) factory.generatePrivate(keySpec);
}
}
The regular expression ...
static final String OPENSSL_ENCRYPTED_RSA_PRIVATEKEY_REGEX = "\\s*"
+ "-----BEGIN RSA PUBLIC KEY-----" + "\\s*"
+ "Proc-Type: 4,ENCRYPTED" + "\\s*"
+ "DEK-Info:" + "\\s*([^\\s]+)" + "\\s+"
+ "([\\s\\S]*)"
+ "-----END RSA PUBLIC KEY-----" + "\\s*";
static final Pattern OPENSSL_ENCRYPTED_RSA_PRIVATEKEY_PATTERN = Pattern.compile(OPENSSL_ENCRYPTED_RSA_PRIVATEKEY_REGEX);
the fromHex(...) method ...
public static byte[] fromHex(String hexString)
{
byte[] bytes = new byte[hexString.length() / 2];
for (int i = 0; i < hexString.length(); i += 2)
{
bytes[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
+ Character.digit(hexString.charAt(i + 1), 16));
}
return bytes;
}

Java DES encrypt, C# DES decrypt

I received an encrypted string from Java, and I can see the Java encrypted source code.
I wrote the decryption code in C#. But always report an error at "FlushFinalBlock". Error message: "System.Security.Cryptography.CryptographicException. Additional information: Incorrect data."
Can any body point out where the problem is in my C# code?
this is java code:
private static byte[] coderByDES(byte[] plainText, String key, int mode)
throws InvalidKeyException, InvalidKeySpecException,
NoSuchAlgorithmException, NoSuchPaddingException,
BadPaddingException, IllegalBlockSizeException,
UnsupportedEncodingException {
SecureRandom sr = new SecureRandom();
byte[] resultKey = makeKey(key);
DESKeySpec desSpec = new DESKeySpec(resultKey);
SecretKey secretKey = SecretKeyFactory.getInstance("DES").generateSecret(desSpec);
Cipher cipher = Cipher.getInstance("DES");
cipher.init(mode, secretKey, sr);
return cipher.doFinal(plainText);
}
private static byte[] makeKey(String key)
throws UnsupportedEncodingException {
byte[] keyByte = new byte[8];
byte[] keyResult = key.getBytes("UTF-8");
for (int i = 0; i < keyResult.length && i < keyByte.length; i++) {
keyByte[i] = keyResult[i];
}
return keyByte;
}
private static String byteArr2HexStr(byte[] arrB) {
int iLen = arrB.length;
StringBuilder sb = new StringBuilder(iLen * 2);
for (int i = 0; i < iLen; i++) {
int intTmp = arrB[i];
while (intTmp < 0) {
intTmp = intTmp + 256;
}
if (intTmp < 16) {
sb.append("0");
}
sb.append(Integer.toString(intTmp, 16));
}
return sb.toString();
}
this is C# code:
public static string DecryptForDES(string input, string key)
{
byte[] inputByteArray = HexStr2ByteArr(input);
byte[] buffArray = null;
using (DESCryptoServiceProvider des = new DESCryptoServiceProvider())
{
des.Key = Encoding.UTF8.GetBytes(key);
des.IV = Encoding.UTF8.GetBytes(key);
des.Mode = System.Security.Cryptography.CipherMode.ECB;
des.Padding = PaddingMode.PKCS7;
System.IO.MemoryStream ms = new System.IO.MemoryStream();
using (CryptoStream cs = new CryptoStream(ms, des.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(inputByteArray, 0, inputByteArray.Length);
cs.FlushFinalBlock();//
cs.Close();
}
buffArray = ms.ToArray();
ms.Close();
}
string str = string.Empty;
if (buffArray != null)
{
str = Encoding.UTF8.GetString(buffArray);
}
return str;
}
public static byte[] HexStr2ByteArr(string strIn)
{
byte[] arrB = Encoding.UTF8.GetBytes(strIn);
int iLen = arrB.Length;
byte[] arrOut = new byte[iLen / 2];
byte[] arrTmp = new byte[2];
for (int i = 0; i < iLen; i = i + 2)
{
string strTmp = Encoding.UTF8.GetString(arrB, i, 2);
arrOut[i / 2] = (byte)Convert.ToInt32(strTmp, 16);
}
return arrOut;
}
Both, the Java encryption part and the C# decryption part work on my machine if the passwords match. Otherwise a System.Security.Cryptography.CryptographicException: 'Bad Data' is thrown. To get the password match replace in the C#-method DecryptForDES
des.Key = Encoding.UTF8.GetBytes(key);
with
des.Key = MakeKey(key);
with the C#-method:
private static byte[] MakeKey(String key)
{
byte[] keyByte = new byte[8];
byte[] keyResult = Encoding.UTF8.GetBytes(key);
for (int i = 0; i<keyResult.Length && i<keyByte.Length; i++) {
keyByte[i] = keyResult[i];
}
return keyByte;
}
corresponding to the Java-method makeKey(String key).
Moreover, remove in the C#-method DecryptForDES
des.IV = Encoding.UTF8.GetBytes(key);
since the ECB-mode doesn't use an IV.
In the following testcase
coderByDES("This is a plain text that needs to be encrypted...", "This is the key used for encryption...", Cipher.ENCRYPT_MODE);
returns the byte-array
a47b1b2c90fb3b7a0ab1f51f328ff55aae3c1eb7789c31c28346696a8b1f27c7413c14e68fe977d3235b5a6f63c07d7a95d912ff22f17ad6
and
DecryptForDES("a47b1b2c90fb3b7a0ab1f51f328ff55aae3c1eb7789c31c28346696a8b1f27c7413c14e68fe977d3235b5a6f63c07d7a95d912ff22f17ad6", "This is the key used for encryption...");
returns the correct plain text.
By the way: As Flydog57 already stated DES is insecure (https://en.wikipedia.org/wiki/Data_Encryption_Standard). And also the ECB mode is not secure (https://crypto.stackexchange.com/questions/20941/why-shouldnt-i-use-ecb-encryption).
Better choices are AES (https://en.wikipedia.org/wiki/Advanced_Encryption_Standard) with CBC or GCM mode (https://crypto.stackexchange.com/questions/2310/what-is-the-difference-between-cbc-and-gcm-mode).

AES 256 Encryption in Java, Decryption in CryptoJS

I have a Java AES 256 encryption code like this :
public class EncryptDecryptTwo {
static {
try {
Field field = Class.forName("javax.crypto.JceSecurity").getDeclaredField("isRestricted");
field.setAccessible(true);
field.set(null, java.lang.Boolean.FALSE);
} catch (Exception ex) {
}
}
private static String keyEnc = "BtDMQ7RfNVoRzJ7GYE32";
// Performs Encryption
public static String encrypt(String plainText) throws Exception {
String passphrase = keyEnc;
byte[] iv = DatatypeConverter.parseHexBinary("2aba86027a6f79dd463b81b0539bacb5");
byte[] salt = DatatypeConverter.parseHexBinary("0f3d1b0d514ca313");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
SecretKeySpec sKey = (SecretKeySpec) generateKeyFromPassword(passphrase, salt);
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
c.init(Cipher.ENCRYPT_MODE, sKey, ivParameterSpec);
byte[] encryptedData = c.doFinal(plainText.getBytes());
return DatatypeConverter.printBase64Binary(encryptedData);
}
public static SecretKey generateKeyFromPassword(String password, byte[] saltBytes) throws GeneralSecurityException {
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), saltBytes, 1, 256);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
SecretKey secretKey = keyFactory.generateSecret(keySpec);
return new SecretKeySpec(secretKey.getEncoded(), "AES");
}
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
}
return data;
}
}
In this case, I hardcoded the salt bytes and iv bytes just to make sure my coding is encrypt the message right, so it can be read by cryptoJS in another server. But when I send the encrypted text by calling the encrypt method, it is always rejected by the cryptoJS, saying that my encrypted message is not correct
The CryptoJS is in another server and it is like this :
var CryptoJSAesJson = {
stringify: function (cipherParams) {
var j = {ct: cipherParams.cipherText.toString(CryptoJS.enc.Base64)};
if (cipherParams.iv) j.iv = cipherParams.iv.toString();
if (cipherParams.salt) j.s = cipherParams.salt.toString();
return JSON.stringify(j);
},
parse: function (jsonStr) {
var j = JSON.parse(jsonStr);
var cipherParams = CryptoJS.lib.CipherParams.create({ciphertext: CryptoJS.enc.Base64.parse(j.ct)});
if (j.iv) cipherParams.iv = CryptoJS.enc.Hex.parse(j.iv)
if (j.s) cipherParams.salt = CryptoJS.enc.Hex.parse(j.s)
return cipherParams;
}
}
var encrypted = CryptoJS.AES.encrypt(JSON.stringify($scope.loginData.password), __ENV.AES_KEY, { format: CryptoJSAesJson}).toString();
Can anybody help me finding what's wrong in my java code? Thank you very much
Edited :
According to Artjom.B's comment and advise, I tried this :
static {
try {
Field field = Class.forName("javax.crypto.JceSecurity").getDeclaredField("isRestricted");
field.setAccessible(true);
field.set(null, java.lang.Boolean.FALSE);
} catch (Exception ex) {
}
}
public static void main(String[] args) throws UnsupportedEncodingException, GeneralSecurityException {
String plaintext = "someplaintext";
String password = "BtDMQ7RfNVoRzWGjS2DK";
int keySize = 256;
int ivSize = 128;
byte[] salt = DatatypeConverter.parseHexBinary("5ba2b0e0bb968f47"); // yes, for testing, I use a fixed salt
byte[] key = new byte[keySize/8];
byte[] iv = new byte[ivSize/8];
EvpKDF(password.getBytes("UTF-8"), keySize, ivSize, salt, key, iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
byte[] encryptedText = cipher.doFinal((plaintext).getBytes());
System.out.println("SALT : " + DatatypeConverter.printHexBinary(salt));
System.out.println("IV : " + DatatypeConverter.printHexBinary(iv));
System.out.println("CT : " + DatatypeConverter.printBase64Binary(encryptedText));
// I sent salt, IV, and ciphertext to CryptoJS
}
public static byte[] EvpKDF(byte[] password, int keySize, int ivSize, byte[] salt, byte[] resultKey, byte[] resultIv) throws NoSuchAlgorithmException {
return EvpKDF(password, keySize, ivSize, salt, 1, "MD5", resultKey, resultIv);
}
public static byte[] EvpKDF(byte[] password, int keySize, int ivSize, byte[] salt, int iterations, String hashAlgorithm, byte[] resultKey, byte[] resultIv) throws NoSuchAlgorithmException {
keySize = keySize / 32;
ivSize = ivSize / 32;
int targetKeySize = keySize + ivSize;
byte[] derivedBytes = new byte[targetKeySize * 4];
int numberOfDerivedWords = 0;
byte[] block = null;
MessageDigest hasher = MessageDigest.getInstance(hashAlgorithm);
while (numberOfDerivedWords < targetKeySize) {
if (block != null) {
hasher.update(block);
}
hasher.update(password);
block = hasher.digest(salt);
hasher.reset();
// Iterations
for (int i = 1; i < iterations; i++) {
block = hasher.digest(block);
hasher.reset();
}
System.arraycopy(block, 0, derivedBytes, numberOfDerivedWords * 4,
Math.min(block.length, (targetKeySize - numberOfDerivedWords) * 4));
numberOfDerivedWords += block.length/4;
}
System.arraycopy(derivedBytes, 0, resultKey, 0, keySize * 4);
System.arraycopy(derivedBytes, keySize * 4, resultIv, 0, ivSize * 4);
return derivedBytes; // key + iv
}
/**
* Copied from http://stackoverflow.com/a/140861
* */
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
But still no avail. The CryptoJS keep saying that I pass an incorrect password.
Can anybody help? Thank you very much

Decrypting an OpenSSL PEM Encoded RSA private key with Java?

I have an encrypted private key and I know the password.
I need to decrypt it using a Java library.
I'd prefer not to use BouncyCastle though, unless there is no other option. Based on previous experience, there is too much change and not enough documentation.
The private key is in this form:
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,56F3A98D9CFFA77A
X5h7SUDStF1tL16lRM+AfZb1UBDQ0D1YbQ6vmIlXiK....
.....
/KK5CZmIGw==
-----END RSA PRIVATE KEY-----
I believe the key data is Base64 encoded since I see \r\n after 64 characters.
I tried the following to decrypt the key:
import java.security.Key;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
public String decrypt(String keyDataStr, String passwordStr){
// This key data start from "X5... to =="
char [] password=passwordStr.toCharArray();
byte [] keyDataBytes=com.sun.jersey.core.util.Base64.decode(keyDataStr);
PBEKeySpec pbeSpec = new PBEKeySpec(password);
EncryptedPrivateKeyInfo pkinfo = new EncryptedPrivateKeyInfo(keyDataBytes);
SecretKeyFactory skf = SecretKeyFactory.getInstance(pkinfo.getAlgName());
Key secret = skf.generateSecret(pbeSpec);
PKCS8EncodedKeySpec keySpec = pkinfo.getKeySpec(secret);
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey pk=kf.generatePrivate(keySpec);
return pk.toString();
}
I get this Exception
java.io.IOException: DerInputStream.getLength(): lengthTag=50, too big.
at sun.security.util.DerInputStream.getLength(DerInputStream.java:561)
at sun.security.util.DerValue.init(DerValue.java:365)
at sun.security.util.DerValue.<init>(DerValue.java:294)
at javax.crypto.EncryptedPrivateKeyInfo.<init> (EncryptedPrivateKeyInfo.java:84)
Am I passing the right parameter to EncryptedPrivateKeyInfo constructor?
How can I make this work?
I tried what Ericsonn suggested, with one small change since I am working Java 7, I could not use Base64.getMimeCoder() instead I used Base64.decode and I am getting this error
I am getting an error like this Input length must be multiple of 8 when decrypting with padded cipher at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:750)
static RSAPrivateKey decrypt(String keyDataStr, String ivHex, String password)
throws GeneralSecurityException, UnsupportedEncodingException
{
byte[] pw = password.getBytes(StandardCharsets.UTF_8);
byte[] iv = h2b(ivHex);
SecretKey secret = opensslKDF(pw, iv);
Cipher cipher = Cipher.getInstance("DESede/CBC/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
byte [] keyBytes=Base64.decode(keyDataStr.getBytes("UTF-8"));
byte[] pkcs1 = cipher.doFinal(keyBytes);
/* See note for definition of "decodeRSAPrivatePKCS1" */
RSAPrivateCrtKeySpec spec = decodeRSAPrivatePKCS1(pkcs1);
KeyFactory rsa = KeyFactory.getInstance("RSA");
return (RSAPrivateKey) rsa.generatePrivate(spec);
}
private static SecretKey opensslKDF(byte[] pw, byte[] iv)
throws NoSuchAlgorithmException
{
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(pw);
md5.update(iv);
byte[] d0 = md5.digest();
md5.update(d0);
md5.update(pw);
md5.update(iv);
byte[] d1 = md5.digest();
byte[] key = new byte[24];
System.arraycopy(d0, 0, key, 0, 16);
System.arraycopy(d1, 0, key, 16, 8);
return new SecretKeySpec(key, "DESede");
}
private static byte[] h2b(CharSequence s)
{
int len = s.length();
byte[] b = new byte[len / 2];
for (int src = 0, dst = 0; src < len; ++dst) {
int hi = Character.digit(s.charAt(src++), 16);
int lo = Character.digit(s.charAt(src++), 16);
b[dst] = (byte) (hi << 4 | lo);
}
return b;
}
static RSAPrivateCrtKeySpec decodeRSAPrivatePKCS1(byte[] encoded)
{
ByteBuffer input = ByteBuffer.wrap(encoded);
if (der(input, 0x30) != input.remaining())
throw new IllegalArgumentException("Excess data");
if (!BigInteger.ZERO.equals(derint(input)))
throw new IllegalArgumentException("Unsupported version");
BigInteger n = derint(input);
BigInteger e = derint(input);
BigInteger d = derint(input);
BigInteger p = derint(input);
BigInteger q = derint(input);
BigInteger ep = derint(input);
BigInteger eq = derint(input);
BigInteger c = derint(input);
return new RSAPrivateCrtKeySpec(n, e, d, p, q, ep, eq, c);
}
private static BigInteger derint(ByteBuffer input)
{
byte[] value = new byte[der(input, 0x02)];
input.get(value);
return new BigInteger(+1, value);
}
private static int der(ByteBuffer input, int exp)
{
int tag = input.get() & 0xFF;
if (tag != exp)
throw new IllegalArgumentException("Unexpected tag");
int n = input.get() & 0xFF;
if (n < 128)
return n;
n &= 0x7F;
if ((n < 1) || (n > 2))
throw new IllegalArgumentException("Invalid length");
int len = 0;
while (n-- > 0) {
len <<= 8;
len |= input.get() & 0xFF;
}
return len;
}
1640 is keyDataStr.length() and 1228 is keyBytes.length
You need to use a non-standard, OpenSSL method for deriving the decryption key. Then use that to decrypt the PKCS-#1–encoded key—what you are working with is not a PKCS #8 envelope. You'll also need the IV from the header as input to these processes.
It looks something like this:
static RSAPrivateKey decrypt(String keyDataStr, String ivHex, String password)
throws GeneralSecurityException
{
byte[] pw = password.getBytes(StandardCharsets.UTF_8);
byte[] iv = h2b(ivHex);
SecretKey secret = opensslKDF(pw, iv);
Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
byte[] pkcs1 = cipher.doFinal(Base64.getMimeDecoder().decode(keyDataStr));
/* See note for definition of "decodeRSAPrivatePKCS1" */
RSAPrivateCrtKeySpec spec = decodeRSAPrivatePKCS1(pkcs1);
KeyFactory rsa = KeyFactory.getInstance("RSA");
return (RSAPrivateKey) rsa.generatePrivate(spec);
}
private static SecretKey opensslKDF(byte[] pw, byte[] iv)
throws NoSuchAlgorithmException
{
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(pw);
md5.update(iv);
byte[] d0 = md5.digest();
md5.update(d0);
md5.update(pw);
md5.update(iv);
byte[] d1 = md5.digest();
byte[] key = new byte[24];
System.arraycopy(d0, 0, key, 0, 16);
System.arraycopy(d1, 0, key, 16, 8);
return new SecretKeySpec(key, "DESede");
}
private static byte[] h2b(CharSequence s)
{
int len = s.length();
byte[] b = new byte[len / 2];
for (int src = 0, dst = 0; src < len; ++dst) {
int hi = Character.digit(s.charAt(src++), 16);
int lo = Character.digit(s.charAt(src++), 16);
b[dst] = (byte) (hi << 4 | lo);
}
return b;
}
This is already a lot of code, so I will link to another answer for the definition of the decodeRSAPrivatePKCS1() method.
Java code example below shows how to construct the decryption key to obtain the underlying RSA key from an encrypted private key created using the openssl 1.0.x genrsa command; specifically from the following genrsa options that may have been leveraged:
-des encrypt the generated key with DES in cbc mode
-des3 encrypt the generated key with DES in ede cbc mode (168 bit key)
-aes128, -aes192, -aes256 encrypt PEM output with cbc aes
Above options result in encrypted RSA private key of the form ...
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AAA,BBB
...
Where AAA would be one of:
DES-CBC, DES-EDE3-CBC, AES-128-CBC, AES-192-CBC, AES-256-CBC
AND BBB is the hex-encoded IV value
KeyFactory factory = KeyFactory.getInstance("RSA");
KeySpec keySpec = null;
RSAPrivateKey privateKey = null;
Matcher matcher = OPENSSL_ENCRYPTED_RSA_PRIVATEKEY_PATTERN.matcher(pemContents);
if (matcher.matches())
{
String encryptionDetails = matcher.group(1).trim(); // e.g. AES-256-CBC,XXXXXXX
String encryptedKey = matcher.group(2).replaceAll("\\s", ""); // remove tabs / spaces / newlines / carriage return etc
System.out.println("PEM appears to be OpenSSL Encrypted RSA Private Key; Encryption details : "
+ encryptionDetails + "; Key : " + encryptedKey);
byte[] encryptedBinaryKey = java.util.Base64.getDecoder().decode(encryptedKey);
String[] encryptionDetailsParts = encryptionDetails.split(",");
if (encryptionDetailsParts.length == 2)
{
String encryptionAlgorithm = encryptionDetailsParts[0];
String encryptedAlgorithmParams = encryptionDetailsParts[1]; // i.e. the initialization vector in hex
byte[] pw = new String(password).getBytes(StandardCharsets.UTF_8);
byte[] iv = fromHex(encryptedAlgorithmParams);
MessageDigest digest = MessageDigest.getInstance("MD5");
// we need to come up with the encryption key
// first round digest based on password and first 8-bytes of IV ..
digest.update(pw);
digest.update(iv, 0, 8);
byte[] round1Digest = digest.digest(); // The digest is reset after this call is made.
// second round digest based on first round digest, password, and first 8-bytes of IV ...
digest.update(round1Digest);
digest.update(pw);
digest.update(iv, 0, 8);
byte[] round2Digest = digest.digest();
Cipher cipher = null;
SecretKey secretKey = null;
byte[] key = null;
byte[] pkcs1 = null;
if ("AES-256-CBC".equals(encryptionAlgorithm))
{
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
key = new byte[32]; // 256 bit key (block size still 128-bit)
System.arraycopy(round1Digest, 0, key, 0, 16);
System.arraycopy(round2Digest, 0, key, 16, 16);
secretKey = new SecretKeySpec(key, "AES");
}
else if ("AES-192-CBC".equals(encryptionAlgorithm))
{
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
key = new byte[24]; // key size of 24 bytes
System.arraycopy(round1Digest, 0, key, 0, 16);
System.arraycopy(round2Digest, 0, key, 16, 8);
secretKey = new SecretKeySpec(key, "AES");
}
else if ("AES-128-CBC".equals(encryptionAlgorithm))
{
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
key = new byte[16]; // 128 bit key
System.arraycopy(round1Digest, 0, key, 0, 16);
secretKey = new SecretKeySpec(key, "AES");
}
else if ("DES-EDE3-CBC".equals(encryptionAlgorithm))
{
cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
key = new byte[24]; // key size of 24 bytes
System.arraycopy(round1Digest, 0, key, 0, 16);
System.arraycopy(round2Digest, 0, key, 16, 8);
secretKey = new SecretKeySpec(key, "DESede");
}
else if ("DES-CBC".equals(encryptionAlgorithm))
{
cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
key = new byte[8]; // key size of 8 bytes
System.arraycopy(round1Digest, 0, key, 0, 8);
secretKey = new SecretKeySpec(key, "DES");
}
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
pkcs1 = cipher.doFinal(encryptedBinaryKey);
keySpec = pkcs1ParsePrivateKey(pkcs1);
privateKey = (RSAPrivateKey) factory.generatePrivate(keySpec);
}
}
The regular expression ...
static final String OPENSSL_ENCRYPTED_RSA_PRIVATEKEY_REGEX = "\\s*"
+ "-----BEGIN RSA PUBLIC KEY-----" + "\\s*"
+ "Proc-Type: 4,ENCRYPTED" + "\\s*"
+ "DEK-Info:" + "\\s*([^\\s]+)" + "\\s+"
+ "([\\s\\S]*)"
+ "-----END RSA PUBLIC KEY-----" + "\\s*";
static final Pattern OPENSSL_ENCRYPTED_RSA_PRIVATEKEY_PATTERN = Pattern.compile(OPENSSL_ENCRYPTED_RSA_PRIVATEKEY_REGEX);
the fromHex(...) method ...
public static byte[] fromHex(String hexString)
{
byte[] bytes = new byte[hexString.length() / 2];
for (int i = 0; i < hexString.length(); i += 2)
{
bytes[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
+ Character.digit(hexString.charAt(i + 1), 16));
}
return bytes;
}

Bouncycastle java encoding/decoding. Missing bytes?

I need to encode/decode a text using CBC Rijndael encryption.
Input:
The force is strong in this looooooooooooooooooo000000000oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong String
Encoded input: ?†´Ú½mΗ“AŽyÝ¢ƒô]5X-å;’Bdž.̵¼èüÈíÖXÈ*©Ã¼ç–hKBµ$híƒEu-ȸU ¤‘AÓÈÿ?Ÿûä¸:OW?B>ÐZ²ñ
,­zÅë(C’®5ÐixRópE%€.#vhrm6µ5©bŠ?Ç¡$q¿J^÷g“e†ì??bt ì%q‘ÕQÚ5µã?ƒ
Decoded input:
"The force is strong in this looooooooooooooooooo000000000ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo " - and 32 spaces in place of the ending (byte value is 0)
I'm missing the final bytes. Can anyone tell me why ?
This is my code:
public class BouncyDecoder {
byte[] IV = null;
byte[] encryptionKey = null;
Cipher cipher;
SecretKeySpec key;
BlockCipher blockCipher;
ParametersWithIV _param;
PaddedBufferedBlockCipher mode;
int blockSize;
public BouncyDecoder() {
Security.addProvider(new BouncyCastleProvider());
try {
IV = "1234567891234567891234567891234".getBytes("UTF-8");
encryptionKey = "1234567891123453456789123456781".getBytes("UTF-8");
blockCipher = new CBCBlockCipher(new RijndaelEngine(256));
_param = new ParametersWithIV(new KeyParameter(encryptionKey), IV);
mode = new PaddedBufferedBlockCipher(blockCipher);
blockSize = blockCipher.getBlockSize();
} catch (Exception e) {
}
}
public byte[] decrypt(byte[] encodedText) {
byte[] decoded = new byte[mode.getOutputSize(encodedText.length)];
try {
mode.init(false, _param);
int bytesProcessed = 0;
int i=0;
for (i = 0; i < (encodedText.length / 32) ; i++){
bytesProcessed += mode.processBytes(encodedText, i * blockSize, blockSize, decoded, bytesProcessed);
}
mode.doFinal(decoded, (i-1)*blockSize);
} catch (Exception e) {
}
return decoded;
}
public byte[] encrypt(byte[] normalText) {
byte[] encryptedText = new byte[mode.getOutputSize(normalText.length)];
try {
mode.init(true, _param);
int bytesProcessed = 0;
int i=0;
for (i = 0; i < (normalText.length / 32); i++) {
bytesProcessed += mode
.processBytes(normalText, i * blockSize, blockSize, encryptedText, bytesProcessed);
}
mode.doFinal(encryptedText, (i-1)*blockSize);
} catch (Exception e) {
e.printStackTrace();
}
return encryptedText;
}
}
Your loop doesn't seem process all of the bytes in input string:
for (i = 0; i < (normalText.length / 32); i++) {
bytesProcessed += mode
.processBytes(normalText, i * blockSize, blockSize, encryptedText, bytesProcessed);
}
it only processes bytes from 0 to (text.Length/32)*blockSize.
So if length of input array is 35 bytes, last 3 bytes are never getting processed.
What about using something like this instead:
bytesProcessed = mode.processBytes(normalText, 0, normalText.length, encryptedText,0);
//second argument of doFinal is offset in output buffer.
mode.doFinal(encryptedText, bytesProcessed);
If this one is going to work you'll definitely know that the problem is off-by-one error in loop counter.
UPDATE: Or you can try something like this if you want to encrypt a block at a time:
for(int i=0; i<=(normalText.length/blockSize); i++) {
int offset = i*blockSize;
//To handle last block of bytes in input
int len = Math.min(blockSize,normalText.length-offset);
bytesProcessed += mode.processBytes(normalText,offset,len,encryptedText,bytesProcessed);
}
mode.doFinal(encryptedText, bytesProcessed);
Same goes for decryption

Categories

Resources