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
Related
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;
}
I used ricmoo/aes-js to encrypt node server response,
Cypher.js
"use strict";
var aesjs = require("aes-js");
var sha256 = require("js-sha256");
const getKeyArray = function() {
let buffer = sha256.arrayBuffer("mykey");
let keyArray = new Uint8Array(buffer);
const keySize = 16;
let arr = new Array();
for (var i = 0; i < keySize; i++) {
arr.push(keyArray[i]);
}
return arr;
};
module.exports = {
getKey: function() {
return getKeyArray();
},
encrypt: function(text) {
var textBytes = aesjs.utils.utf8.toBytes(text);
// The counter is optional, and if omitted will begin at 1
var aesCtr = new aesjs.ModeOfOperation.ctr(
getKeyArray(),
new aesjs.Counter(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER))
);
var counterArray = aesCtr._counter._counter.slice()
var encryptedBytes = aesCtr.encrypt(textBytes);
// To print or store the binary data, you may convert it to hex
var encryptedHex = aesjs.utils.hex.fromBytes(encryptedBytes);
var ivHex = aesjs.utils.hex.fromBytes(counterArray);
return ivHex + ":" + encryptedHex;
},
decrypt: function(encryptedHex) {
let split = encryptedHex.split(":");
// When ready to decrypt the hex string, convert it back to bytes
var encryptedBytes = aesjs.utils.hex.toBytes(split[1]);
let ivHex = split[0];
var ivBytes = aesjs.utils.hex.toBytes(ivHex);
var counter = new aesjs.Counter(ivBytes);
// The counter mode of operation maintains internal state, so to
// decrypt a new instance must be instantiated.
var aesCtr = new aesjs.ModeOfOperation.ctr(getKeyArray(), ivBytes);
var decryptedBytes = aesCtr.decrypt(encryptedBytes);
// Convert our bytes back into text
var decryptedText = aesjs.utils.utf8.fromBytes(decryptedBytes);
return decryptedText;
}
};
And the decryption in Java
Cypher.java
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
class Cypher {
private final static char[] hexArray = "0123456789ABCDEF".toCharArray();
private static String KEY = "mykey";
/**
* Decrypt a given hex string,
* #param hexString
* #return
*/
static String decrypt(String hexString) throws Exception{
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
String ivHex = hexString.split(":")[0];
hexString = hexString.split(":")[1];
IvParameterSpec ivSpec = new IvParameterSpec(hexStringToByteArray(ivHex));
cipher.init(Cipher.DECRYPT_MODE, getEncryptionKey(KEY), ivSpec);
byte[] decrypted = cipher.doFinal(hexStringToByteArray(hexString));
return new String(decrypted);
}
private static SecretKeySpec getEncryptionKey(String key) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
digest.update(key.getBytes("UTF-8"));
byte[] keyBytes = new byte[16];
System.arraycopy(digest.digest(), 0, keyBytes, 0, keyBytes.length);
SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES");
return secretKeySpec;
}
static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for ( int j = 0; j < bytes.length; j++ ) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
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;
}
}
My question is:
1- Is this code above acceptable or it has any major issue?
2- How can I improve it?
Edit 2
I have updated the question, now the code in question uses random IV in the encryption & decryption process.
Edit
Please do not use the code below, because it doesn't generate the IVs randomly, see the comments below.
Old Answer
It was not easy at all, but finally I got it to work, here is my full working code (encryption on node.js and decryption on Android):
Cypher.js
"use strict";
var aesjs = require("aes-js");
var sha256 = require("js-sha256");
const getKeyArray = function() { // decryption on Android doesn't support 256 bit keys with AES/CTR, so I'm using only 128 bits
let buffer = sha256.arrayBuffer("mystrongkey");
let keyArray = new Uint8Array(buffer);
const keySize = 16;
let arr = new Array;
for (var i = 0; i < keySize; i++) {
arr.push(keyArray[i]);
}
return arr;
}
module.exports = {
getKey: function() {
return getKeyArray();
},
getIV: function() {
return new aesjs.Counter(5);
},
encrypt: function(text) {
var textBytes = aesjs.utils.utf8.toBytes(text);
// The counter is optional, and if omitted will begin at 1
var aesCtr = new aesjs.ModeOfOperation.ctr(getKeyArray(), this.getIV());
var encryptedBytes = aesCtr.encrypt(textBytes);
// To print or store the binary data, you may convert it to hex
var encryptedHex = aesjs.utils.hex.fromBytes(encryptedBytes);
return encryptedHex;
},
decrypt: function(encryptedHex) {
// When ready to decrypt the hex string, convert it back to bytes
var encryptedBytes = aesjs.utils.hex.toBytes(encryptedHex);
// The counter mode of operation maintains internal state, so to
// decrypt a new instance must be instantiated.
var aesCtr = new aesjs.ModeOfOperation.ctr(getKeyArray(), this.getIV());
var decryptedBytes = aesCtr.decrypt(encryptedBytes);
// Convert our bytes back into text
var decryptedText = aesjs.utils.utf8.fromBytes(decryptedBytes);
return decryptedText;
}
};
Cypher.java
package com.mypackage;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
class Cypher {
private final static char[] hexArray = "0123456789ABCDEF".toCharArray();
private static String KEY = "mystrongkey";
/**
* Decrypt a given hex string,
* 08768efebc = Hello
* #param hexString
* #return
*/
static String decrypt(String hexString) throws Exception{
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
IvParameterSpec ivSpec = new IvParameterSpec(new byte[] { // got this one by console.log(Cypher.getIv()) from Cypher.js
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
5
});
cipher.init(Cipher.DECRYPT_MODE, getEncryptionKey(KEY), ivSpec);
byte[] decrypted = cipher.doFinal(hexStringToByteArray(hexString));
return new String(decrypted);
}
static String encrypt(String string) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
IvParameterSpec ivSpec = new IvParameterSpec(new byte[] {
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
5
});
cipher.init(Cipher.ENCRYPT_MODE, getEncryptionKey(KEY), ivSpec);
byte[] encrypted = cipher.doFinal(string.getBytes());
return bytesToHex(encrypted);
}
private static SecretKeySpec getEncryptionKey(String key) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
digest.update(key.getBytes("UTF-8"));
byte[] keyBytes = new byte[16];
System.arraycopy(digest.digest(), 0, keyBytes, 0, keyBytes.length);
SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES");
return secretKeySpec;
}
static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for ( int j = 0; j < bytes.length; j++ ) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
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;
}
}
Hope this will save time of somebody trying to decrypt from node.js
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;
}
I have to create a weservice that sends an encrypted String with AES, a salt size of 32 and a given password. I'm trying to make my code work but when I try to decrypt a String that they gave me to check if the decrypting is working I get an error:
Exception in thread "main" javax.crypto.BadPaddingException: Given final block not properly padded
My code is below:
import javax.crypto.*;
import javax.crypto.spec.*;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
/**
* parts of this code were copied from the StandardPBEByteEncryptor class from the Jasypt (www.jasypt.org) project
*/
public class AESCrypt {
//private final String KEY_ALGORITHM = "PBEWITHSHA256AND128BITAES-CBC-BC";
private final String KEY_ALGORITHM = "PBEWithSHA256And256BitAES-CBC-BC";
private final String MODE_PADDING = "/CBC/PKCS5Padding";
private final int DEFAULT_SALT_SIZE_BYTES = 32;
private final SecureRandom rand;
private final String passwd = "8g5qT74KdUY";
public AESCrypt() throws Exception {
rand = SecureRandom.getInstance("SHA1PRNG");
}
private byte[] generateSalt(int size) {
byte[] salt = new byte[size];
rand.nextBytes(salt);
return salt;
}
private SecretKey generateKey(String algorithm, int keySize, byte[] salt) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException {
SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_ALGORITHM);
PBEKeySpec pbeKeySpec = new PBEKeySpec(passwd.toCharArray(), salt, 100000);
SecretKey tmpKey = factory.generateSecret(pbeKeySpec);
byte[] keyBytes = new byte[keySize / 8];
System.arraycopy(tmpKey.getEncoded(), 0, keyBytes, 0, keyBytes.length);
return new SecretKeySpec(keyBytes, algorithm);
}
private byte[] generateIV(Cipher cipher) {
byte[] iv = new byte[cipher.getBlockSize()];
rand.nextBytes(iv);
return iv;
}
private byte[] appendArrays(byte[] firstArray, byte[] secondArray) {
final byte[] result = new byte[firstArray.length + secondArray.length];
System.arraycopy(firstArray, 0, result, 0, firstArray.length);
System.arraycopy(secondArray, 0, result, firstArray.length, secondArray.length);
return result;
}
public byte[] encrypt(String algorithm, int keySize, final byte[] message) throws Exception {
Cipher cipher = Cipher.getInstance(algorithm + MODE_PADDING);
// The salt size for the chosen algorithm is set to be equal
// to the algorithm's block size (if it is a block algorithm).
int saltSizeBytes = DEFAULT_SALT_SIZE_BYTES;
int algorithmBlockSize = cipher.getBlockSize();
if (algorithmBlockSize > 0) {
saltSizeBytes = algorithmBlockSize;
}
// Create salt
final byte[] salt = generateSalt(saltSizeBytes);
SecretKey key = generateKey(algorithm, keySize, salt);
// create a new IV for each encryption
final IvParameterSpec ivParamSpec = new IvParameterSpec(generateIV(cipher));
// Perform encryption using the Cipher
cipher.init(Cipher.ENCRYPT_MODE, key, ivParamSpec);
byte[] encryptedMessage = cipher.doFinal(message);
// append the IV and salt
encryptedMessage = appendArrays(ivParamSpec.getIV(), encryptedMessage);
encryptedMessage = appendArrays(salt, encryptedMessage);
return encryptedMessage;
}
public byte[] decrypt(String algorithm, int keySize, final byte[] encryptedMessage) throws Exception {
Cipher cipher = Cipher.getInstance(algorithm + MODE_PADDING);
// determine the salt size for the first layer of encryption
int saltSizeBytes = DEFAULT_SALT_SIZE_BYTES;
int algorithmBlockSize = cipher.getBlockSize();
if (algorithmBlockSize > 0) {
saltSizeBytes = algorithmBlockSize;
}
System.out.println("saltSizeBytes:"+saltSizeBytes);
byte[] decryptedMessage = new byte[encryptedMessage.length];
System.arraycopy(encryptedMessage, 0, decryptedMessage, 0, encryptedMessage.length);
// extract the salt and IV from the incoming message
byte[] salt = null;
byte[] iv = null;
byte[] encryptedMessageKernel = null;
final int saltStart = 0;
final int saltSize = (saltSizeBytes < decryptedMessage.length ? saltSizeBytes : decryptedMessage.length);
//final int saltSize = 32;
//System.out.println("saltSize:"+saltSize);
final int ivStart = (saltSizeBytes < decryptedMessage.length ? saltSizeBytes : decryptedMessage.length);
final int ivSize = cipher.getBlockSize();
final int encMesKernelStart = (saltSizeBytes + ivSize < decryptedMessage.length ? saltSizeBytes + ivSize : decryptedMessage.length);
final int encMesKernelSize = (saltSizeBytes + ivSize < decryptedMessage.length ? (decryptedMessage.length - saltSizeBytes - ivSize) : 0);
salt = new byte[saltSize];
iv = new byte[ivSize];
System.out.println("saltSize:"+saltSize);
System.out.println("ivSize:"+ivSize);
encryptedMessageKernel = new byte[encMesKernelSize];
System.out.println("encryptedMessageKernel");
System.arraycopy(decryptedMessage, saltStart, salt, 0, saltSize);
System.arraycopy(decryptedMessage, ivStart, iv, 0, ivSize);
System.arraycopy(decryptedMessage, encMesKernelStart, encryptedMessageKernel, 0, encMesKernelSize);
SecretKey key = generateKey(algorithm, keySize, salt);
System.out.println("ekey");
IvParameterSpec ivParamSpec = new IvParameterSpec(iv);
// Perform decryption using the Cipher
cipher.init(Cipher.DECRYPT_MODE, key, ivParamSpec);
decryptedMessage = cipher.doFinal(encryptedMessageKernel);
// Return the results
return decryptedMessage;
}
Now I have this information I know it was crypted and I would like to decrypt:
Original String: 12334567
Crypted String: SsH6NO9a64g0U7szvFwSbCkdUF5dNgmxgpt2jU/nFVntG3r2nYxgxLRXri4MW9Z2
Password: 8g5qT74KdUY
When I try to decrypt the SsH... I get the given error. Where is the problem? This is what I do:
String toDecrypt = "SsH6NO9a64g0U7szvFwSbCkdUF5dNgmxgpt2jU/nFVntG3r2nYxgxLRXri4MW9Z2";
byte[] criptata = Base64.decode(toDecrypt);
byte[] decriptata = engine.decrypt("AES", 128, criptata);
String msgdecriptato = new String(decriptata);
This gives me an error.
Here is the code in C# they use to do decrypt:
private const int SaltSize = 32;
/// <summary>
/// Decrypts the ciphertext using the Key.
/// </summary>
/// <param name="ciphertext">The ciphertext to decrypt.</param>
/// <param name="key">The plain text encryption key.</param>
/// <returns>The decrypted text.</returns>
public string Decrypt(string ciphertext, string key)
{
if (string.IsNullOrEmpty(ciphertext))
throw new ArgumentNullException("ciphertext");
if (string.IsNullOrEmpty(key))
throw new ArgumentNullException("key");
// Extract the salt from our ciphertext
var allTheBytes = Convert.FromBase64String(ciphertext);
var saltBytes = allTheBytes.Take(SaltSize).ToArray();
var ciphertextBytes = allTheBytes.Skip(SaltSize).Take(allTheBytes.Length - SaltSize).ToArray();
using (var keyDerivationFunction = new Rfc2898DeriveBytes(key, saltBytes))
{
// Derive the previous IV from the Key and Salt
var keyBytes = keyDerivationFunction.GetBytes(32);
var ivBytes = keyDerivationFunction.GetBytes(16);
// Create a decrytor to perform the stream transform.
// Create the streams used for decryption.
// The default Cipher Mode is CBC and the Padding is PKCS7 which are both good
using (var aesManaged = new AesManaged())
using (var decryptor = aesManaged.CreateDecryptor(keyBytes, ivBytes))
using (var memoryStream = new MemoryStream(ciphertextBytes))
using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
using (var streamReader = new StreamReader(cryptoStream))
{
// Return the decrypted bytes from the decrypting stream.
return streamReader.ReadToEnd();
}
}
}
Any hint?
I've run your code and It seems to be a problem with your original string. You have to be aware that if it was encrypted with 128bit key it cannot be encrypted with 256bit key. And if the key-sizes do not match you get a bad padding error. This is your code with a main that works:
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.InvalidKeySpecException;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;
/**
* parts of this code were copied from the StandardPBEByteEncryptor class from
* the Jasypt (www.jasypt.org) project
*/
public class AESCrypt {
// private final String KEY_ALGORITHM = "PBEWITHSHA256AND128BITAES-CBC-BC";
private final String KEY_ALGORITHM = "PBEWithSHA256And256BitAES-CBC-BC";
private final String MODE_PADDING = "/CBC/PKCS5Padding";
private final int DEFAULT_SALT_SIZE_BYTES = 32;
private final SecureRandom rand;
private final String passwd = "8g5qT74KdUY";
public AESCrypt() throws Exception {
rand = SecureRandom.getInstance("SHA1PRNG");
}
private byte[] generateSalt(int size) {
byte[] salt = new byte[size];
rand.nextBytes(salt);
return salt;
}
private SecretKey generateKey(String algorithm, int keySize, byte[] salt)
throws NoSuchProviderException, NoSuchAlgorithmException,
InvalidKeySpecException {
SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_ALGORITHM);
PBEKeySpec pbeKeySpec = new PBEKeySpec(passwd.toCharArray(), salt,
100000);
SecretKey tmpKey = factory.generateSecret(pbeKeySpec);
byte[] keyBytes = new byte[keySize / 8];
System.arraycopy(tmpKey.getEncoded(), 0, keyBytes, 0, keyBytes.length);
return new SecretKeySpec(keyBytes, algorithm);
}
private byte[] generateIV(Cipher cipher) {
byte[] iv = new byte[cipher.getBlockSize()];
rand.nextBytes(iv);
return iv;
}
private byte[] appendArrays(byte[] firstArray, byte[] secondArray) {
final byte[] result = new byte[firstArray.length + secondArray.length];
System.arraycopy(firstArray, 0, result, 0, firstArray.length);
System.arraycopy(secondArray, 0, result, firstArray.length,
secondArray.length);
return result;
}
public byte[] encrypt(String algorithm, int keySize, final byte[] message)
throws Exception {
Cipher cipher = Cipher.getInstance(algorithm + MODE_PADDING);
// The salt size for the chosen algorithm is set to be equal
// to the algorithm's block size (if it is a block algorithm).
int saltSizeBytes = DEFAULT_SALT_SIZE_BYTES;
int algorithmBlockSize = cipher.getBlockSize();
if (algorithmBlockSize > 0) {
saltSizeBytes = algorithmBlockSize;
}
// Create salt
final byte[] salt = generateSalt(saltSizeBytes);
SecretKey key = generateKey(algorithm, keySize, salt);
// create a new IV for each encryption
final IvParameterSpec ivParamSpec = new IvParameterSpec(
generateIV(cipher));
// Perform encryption using the Cipher
cipher.init(Cipher.ENCRYPT_MODE, key, ivParamSpec);
byte[] encryptedMessage = cipher.doFinal(message);
// append the IV and salt
encryptedMessage = appendArrays(ivParamSpec.getIV(), encryptedMessage);
encryptedMessage = appendArrays(salt, encryptedMessage);
return encryptedMessage;
}
public byte[] decrypt(String algorithm, int keySize,
final byte[] encryptedMessage) throws Exception {
Cipher cipher = Cipher.getInstance(algorithm + MODE_PADDING);
// determine the salt size for the first layer of encryption
int saltSizeBytes = DEFAULT_SALT_SIZE_BYTES;
int algorithmBlockSize = cipher.getBlockSize();
if (algorithmBlockSize > 0) {
saltSizeBytes = algorithmBlockSize;
}
System.out.println("saltSizeBytes:" + saltSizeBytes);
byte[] decryptedMessage = new byte[encryptedMessage.length];
System.arraycopy(encryptedMessage, 0, decryptedMessage, 0,
encryptedMessage.length);
// extract the salt and IV from the incoming message
byte[] salt = null;
byte[] iv = null;
byte[] encryptedMessageKernel = null;
final int saltStart = 0;
final int saltSize = (saltSizeBytes < decryptedMessage.length ? saltSizeBytes
: decryptedMessage.length);
// final int saltSize = 32;
// System.out.println("saltSize:"+saltSize);
final int ivStart = (saltSizeBytes < decryptedMessage.length ? saltSizeBytes
: decryptedMessage.length);
final int ivSize = cipher.getBlockSize();
final int encMesKernelStart = (saltSizeBytes + ivSize < decryptedMessage.length ? saltSizeBytes
+ ivSize
: decryptedMessage.length);
final int encMesKernelSize = (saltSizeBytes + ivSize < decryptedMessage.length ? (decryptedMessage.length
- saltSizeBytes - ivSize)
: 0);
salt = new byte[saltSize];
iv = new byte[ivSize];
System.out.println("saltSize:" + saltSize);
System.out.println("ivSize:" + ivSize);
encryptedMessageKernel = new byte[encMesKernelSize];
System.out.println("encryptedMessageKernel");
System.arraycopy(decryptedMessage, saltStart, salt, 0, saltSize);
System.arraycopy(decryptedMessage, ivStart, iv, 0, ivSize);
System.arraycopy(decryptedMessage, encMesKernelStart,
encryptedMessageKernel, 0, encMesKernelSize);
SecretKey key = generateKey(algorithm, keySize, salt);
System.out.println("ekey");
IvParameterSpec ivParamSpec = new IvParameterSpec(iv);
// Perform decryption using the Cipher
cipher.init(Cipher.DECRYPT_MODE, key, ivParamSpec);
decryptedMessage = cipher.doFinal(encryptedMessageKernel);
// Return the results
return decryptedMessage;
}
public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastleProvider());
AESCrypt aesCrypt = new AESCrypt();
String originalText = "1234567";
String toDecrypt = new String(Base64.encode(aesCrypt.encrypt("AES", 256, originalText.getBytes())));
System.out.println(toDecrypt);
byte[] criptata = Base64.decode(toDecrypt);
byte[] decriptata = aesCrypt.decrypt("AES", 256, criptata);
String msgdecriptato = new String(decriptata);
System.out.println(msgdecriptato);
if (!originalText.equals(msgdecriptato)) {
throw new IllegalStateException("Strings do not match!");
}
}
}
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;
}