The Openbravo software and its derivatives (e.g. unicentaopos) have the following implementation of encryption to store the database password in a plain configuration file.
package com.openbravo.pos.util;
import java.io.UnsupportedEncodingException;
import java.security.*;
import javax.crypto.*;
/**
*
* #author JG uniCenta
*/
public class AltEncrypter {
private Cipher cipherDecrypt;
private Cipher cipherEncrypt;
/** Creates a new instance of Encrypter
* #param passPhrase */
public AltEncrypter(String passPhrase) {
try {
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
sr.setSeed(passPhrase.getBytes("UTF8"));
KeyGenerator kGen = KeyGenerator.getInstance("DESEDE");
kGen.init(168, sr);
Key key = kGen.generateKey();
cipherEncrypt = Cipher.getInstance("DESEDE/ECB/PKCS5Padding");
cipherEncrypt.init(Cipher.ENCRYPT_MODE, key);
cipherDecrypt = Cipher.getInstance("DESEDE/ECB/PKCS5Padding");
cipherDecrypt.init(Cipher.DECRYPT_MODE, key);
} catch (UnsupportedEncodingException | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) {
}
}
/**
*
* #param str
* #return
*/
public String encrypt(String str) {
try {
return StringUtils.byte2hex(cipherEncrypt.doFinal(str.getBytes("UTF8")));
} catch (UnsupportedEncodingException | BadPaddingException | IllegalBlockSizeException e) {
}
return null;
}
/**
*
* #param str
* #return
*/
public String decrypt(String str) {
try {
return new String(cipherDecrypt.doFinal(StringUtils.hex2byte(str)), "UTF8");
} catch (UnsupportedEncodingException | BadPaddingException | IllegalBlockSizeException e) {
}
return null;
}
}
To encrypt, the following is used (only the password is encrypted):
config.setProperty("db.user", jtxtDbUser.getText());
AltEncrypter cypher = new AltEncrypter("cypherkey" + jtxtDbUser.getText());
config.setProperty("db.password", "crypt:" + cypher.encrypt(new String(jtxtDbPassword.getPassword())));
To decrypt, the following is used:
String sDBUser = m_App.getProperties().getProperty("db.user");
String sDBPassword = m_App.getProperties().getProperty("db.password");
if (sDBUser != null && sDBPassword != null && sDBPassword.startsWith("crypt:")) {
AltEncrypter cypher = new AltEncrypter("cypherkey" + sDBUser);
sDBPassword = cypher.decrypt(sDBPassword.substring(6));
}
I am working on an independent software module in C# and I'd like to read the database password from that configuration file. Any advice on how to accomplish this?
From analyzing the code, I can deduce that:
The password "encryption" is reversible because it is later used in the software to build database connection strings.
The base passphrase is "cypherkey" + username
The password is stored in the plain file with the format
db.password=crypt:XXX
where XXX is the encrypted password.
Please help me to work out how to decrypt the password. Help on actually reading the plain file is not necessary. Please assume that I already have stored the username and encrypted password (without the "crypt:" part) in variables in the C# program.
I've been trying to modify the existing examples on similar question but they focus on AES and so far I have not been successful with this.
Basically, the following function in C# should be built:
private string DecryptPassword(string username, string encryptedPassword)
How would I do this?
The software is open source and can be found here
One test case: DecryptPassword("mark", "19215E9576DE6A96D5F03FE1D3073DCC") should return the password getmeback. The base passphrase would be cypherkeymark. I have tested in different machines and the "hashed" password is always the same using the same username.
The method used by AltEncrypter to derive a key from the password is terrible. This approach should not be used.
First of all, it's not secure. A key derivation algorithm is not secure unless it is computationally intensive. Instead, use an algorithm like scrypt, bcrypt, or PBKDF2.
Second, the SHA1PRNG algorithm is not well defined. Saying, "it uses SHA-1" isn't sufficient. How often is a hash performed? It's not standardized; you won't be able to request a "SHA1PRNG" on another platform (like .Net), and get the same output.
So, scrap this encryption method and use something easy and secure, written and maintained by knowledgeable people.
Unfortunately, the problems don't end there. The AltEncrypter utility is used in the worst way possible, with a key that isn't secret, to reversibly encrypt an authentication password. This is not secure at all. It allows an attacker to decrypt user passwords and use them against the user's accounts on other systems.
It's almost like the author of this system wanted to create a security catastrophe.
This is a note. I cannot add comments but I think that the algorithm used to encrypt is not SHA1. It is "DESEDE/ECB/PKCS5Padding" look at the line where the cipher to encrypt is created (obtained)
cipherEncrypt = Cipher.getInstance("DESEDE/ECB/PKCS5Padding");
SHA1PRNG is a pseudo-random number generator used to generate a first random number used into the encryption process in order to generate "different" encryptions even when the same plain text is encrypted.
Another important thing is the key used to encrypt, I mean:
KeyGenerator kGen = KeyGenerator.getInstance("DESEDE");
kGen.init(168, sr);
Key key = kGen.generateKey(); <-- this key
this key is used to encrypt and decrypt but I cannot see where it is stored. I mean that it is regenerated every time. It should be stored and retrieved from somewhere and not regenerated because, it is not possible to decrypt any cipher text if it is not used the same key.
This is an answer using some workarounds.
I've tried reimplementing the SHA1PRNG provided the GNU implementation (which is opensource), but it doesn't give the same results as the properitary SUN implementation (so either they're different or I have implemented it in a wrong way). So I've implemented a workaround: Call a java-program to derive the key for us. Yes, this is very cheap, but a working work-around for the time being. If someone sees the mistake in my SHA1PRNG implementation, let me know.
So first, here's a simple Java program which will derive a 168-bit key given a seed using the SHA1PRNG generator. Simply outputs it on stdout, space seperated.
import java.io.UnsupportedEncodingException;
import java.security.*;
import javax.crypto.*;
public class PasswordDeriver {
public static void main(String[] args) throws NoSuchAlgorithmException, UnsupportedEncodingException {
if(args.length == 0){
System.out.println("You need to give the seed as the first argument.");
return;
}
//Use Java to generate the key used for encryption and decryption.
String passPhrase = args[args.length-1];
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
sr.setSeed(passPhrase.getBytes("UTF8"));
KeyGenerator kGen = KeyGenerator.getInstance("DESEDE");
kGen.init(168, sr);
Key key = kGen.generateKey();
//Key is generated, now output it.
//System.out.println("Format: " + key.getFormat());
byte[] k = key.getEncoded();
for(int i=0; i < k.length; i++){
System.out.print(String.format((i == k.length - 1) ? "%X" : "%X ", k[i]));
}
}
}
This is saved as PasswordDeriver.java, compiled using javac <file> and the resulting PasswordDeriver.class is then placed in the same folder as this compiled program: (The actual C# program)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;
using System.IO;
using System.Diagnostics;
namespace OpenbravoDecrypter
{
class Program
{
static void Main(string[] args)
{
var decrypted = Decrypt("19215E9576DE6A96D5F03FE1D3073DCC", "mark");
Console.ReadLine();
}
static string Decrypt(string ciphertext, string username)
{
//Ciphertext is given as a hex string, convert it back to bytes
if(ciphertext.Length % 2 == 1) ciphertext = "0" + ciphertext; //pad a zero left is necessary
byte[] ciphertext_bytes = new byte[ciphertext.Length / 2];
for(int i=0; i < ciphertext.Length; i+=2)
ciphertext_bytes[i / 2] = Convert.ToByte(ciphertext.Substring(i, 2), 16);
//Get an instance of a tripple-des descryption
TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();
tdes.Mode = CipherMode.ECB; //ECB as Cipher Mode
tdes.Padding = PaddingMode.PKCS7; //PKCS7 padding (same as PKCS5, good enough)
byte[] key_bytes = DeriveKeyWorkAround(username);
Console.WriteLine("Derived Key: " + BitConverter.ToString(key_bytes));
//Start the decryption, give it the key, and null for the IV.
var decryptor = tdes.CreateDecryptor(key_bytes, null);
//Decrypt it.
var plain = decryptor.TransformFinalBlock(ciphertext_bytes, 0, ciphertext_bytes.Length);
//Output the result as hex string and as UTF8 encoded string
Console.WriteLine("Plaintext Bytes: " + BitConverter.ToString(plain));
var s = Encoding.UTF8.GetString(plain);
Console.WriteLine("Plaintext UTF-8: " + s);
return s;
}
/* Work around the fact that we don't have a C# implementation of SHA1PRNG by calling into a custom-prepared java file..*/
static byte[] DeriveKeyWorkAround(string username)
{
username = "cypherkey" + username;
string procOutput = "";
//Invoke java on our file
Process p = new Process();
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.Arguments = "/c java PasswordDeriver \"" + username + "\"";
p.StartInfo.RedirectStandardOutput = true;
p.OutputDataReceived += (e, d) => procOutput += d.Data;
p.StartInfo.UseShellExecute = false;
p.Start();
p.BeginOutputReadLine();
p.WaitForExit();
//Convert it back
byte[] key = procOutput.Split(' ').Select(hex => Convert.ToByte(hex, 16)).ToArray();
return key;
}
/* This function copies the functionality of the GNU Implementation of SHA1PRNG.
* Currently, it's broken, meaning that it doesn't produce the same output as the SUN implenetation of SHA1PRNG.
* Case 1: the GNU implementation is the same as the SUN implementation, and this re-implementation is just wrong somewhere
* Case 2: the GNU implementation is not the same the SUN implementation, therefore you'd need to reverse engineer some existing
* SUN implementation and correct this method.
*/
static byte[] DeriveKey(string username)
{
//adjust
username = "cypherkey" + username;
byte[] user = Encoding.UTF8.GetBytes(username);
//Do SHA1 magic
var sha1 = new SHA1CryptoServiceProvider();
var seed = new byte[20];
byte[] data = new byte[40];
int seedpos = 0;
int datapos = 0;
//init stuff
byte[] digestdata;
digestdata = sha1.ComputeHash(data);
Array.Copy(digestdata, 0, data, 0, 20);
/* seeding part */
for (int i=0; i < user.Length; i++)
{
seed[seedpos++ % 20] ^= user[i];
}
seedpos %= 20;
/* Generate output bytes */
byte[] bytes = new byte[24]; //we need 24 bytes (= 192 bit / 8)
int loc = 0;
while (loc < bytes.Length)
{
int copy = Math.Min(bytes.Length - loc, 20 - datapos);
if (copy > 0)
{
Array.Copy(data, datapos, bytes, loc, copy);
datapos += copy;
loc += copy;
}
else
{
// No data ready for copying, so refill our buffer.
Array.Copy(seed, 0, data, 20, 20);
byte[] digestdata2 = sha1.ComputeHash(data);
Array.Copy(digestdata2, 0, data, 0, 20);
datapos = 0;
}
}
Console.WriteLine("GENERATED KEY:\n");
for(int i=0; i < bytes.Length; i++)
{
Console.Write(bytes[i].ToString("X").PadLeft(2, '0'));
}
return bytes;
}
}
}
You can see the standard stuff such as initializing a tripple-DES cryptoprovider, giving it a key and computing the decryption of the ciphertext in there. It also contains the currently broken implementation of the SHA1PRNG and the workaround. Given that java is in the PATH of the current environment variable, this program produces the output:
Derived Key: 86-EF-C1-F2-2F-97-D3-F1-34-49-23-89-E3-EC-29-80-02-92-52-40-49-5D-CD-C1
Plaintext Bytes: 67-65-74-6D-65-62-61-63-6B
Plaintext UTF-8: getmeback
So, here you have the decrypt function (encrypting it would the same, just change .CreateDecryptor() to .CreateEncryptor()). If you forget about the code doing the key derivation, the decryption code does its work in only ~20 lines of code. So in review, my answer is a starting point for others who want to make this solution 100% C#. Hope this helps.
Related
I am trying to do AES Encryption using JAVA, I have made multiple attempts, tried a lot of codes and did many changes to finally reach to a place where my encrypted text matches with the encrypted text generated using C# code BUT PARTIALLY. The last block of 32 bits is different. I do not have access to the C# code since it is a 3rd Party Service. Can anyone guide what am I missing?
Conditions Mentioned are to use:
Use 256-bit AES encryption in CBC mode and with PKCS5 padding to encrypt the entire query string using your primary key and initialization vector. (Do not include a message digest in the query string.) The primary key is a 64-digit hexadecimal string and the initialization vector is a 32-digit hexadecimal string.
The sample values I used are:
Aes_IV = 50B666AADBAEDC14C3401E82CD6696D4
Aes_Key = D4612601EDAF9B0852FC0641DC2F273E0F2B9D6E85EBF3833764BF80E09DD89F (my KeyMaterial)
Plain_Text = ss=brock&pw=123456&ts=20190304234431 (input)
Encrypted_Text = 7643C7B400B9A6A2AD0FCFC40AC1B11E51A038A32C84E5560D92C0C49B3B7E0 A072AF44AADB62FA66F047EACA5C6A018 (output)
My Output =
7643C7B400B9A6A2AD0FCFC40AC1B11E51A038A32C84E5560D92C0C49B3B7E0 A38E71E5C846BAA6C31F996AB05AFD089
public static String encrypt( String keyMaterial, String unencryptedString, String ivString ) {
String encryptedString = "";
Cipher cipher;
try {
byte[] secretKey = hexStrToByteArray( keyMaterial );
SecretKey key = new SecretKeySpec( secretKey, "AES" );
cipher = Cipher.getInstance( "AES/CBC/PKCS5Padding" );
IvParameterSpec iv;
iv = new IvParameterSpec( hexStrToByteArray( ivString ) );
cipher.init( Cipher.ENCRYPT_MODE, key, iv );
byte[] plainText = unencryptedString.getBytes( "UTF-8") ;
byte[] encryptedText = cipher.doFinal( plainText );
encryptedString = URLEncoder.encode(byteArrayToHexString( encryptedText ),"UTF-8");
}
catch( InvalidKeyException | InvalidAlgorithmParameterException | UnsupportedEncodingException | IllegalBlockSizeException | BadPaddingException | NoSuchAlgorithmException | NoSuchPaddingException e ) {
System.out.println( "Exception=" +e.toString() );
}
return encryptedString;
}
I have used this for conversions.
public static byte[] hexStrToByteArray ( String input) {
if (input == null) return null;
if (input.length() == 0) return new byte[0];
if ((input.length() % 2) != 0)
input = input + "0";
byte[] result = new byte[input.length() / 2];
for (int i = 0; i < result.length; i++) {
String byteStr = input.substring(2*i, 2*i+2);
result[i] = (byte) Integer.parseInt("0" + byteStr, 16);
}
return result;
}
public static String byteArrayToHexString(byte[] ba) {
String build = "";
for (int i = 0; i < ba.length; i++) {
build += bytesToHexString(ba[i]);
}
return build;
}
public static String bytesToHexString ( byte bt) {
String hexStr ="0123456789ABCDEF";
char ch[] = new char[2];
int value = (int) bt;
ch[0] = hexStr.charAt((value >> 4) & 0x000F);
ch[1] = hexStr.charAt(value & 0x000F);
String str = new String(ch);
return str;
}
Any Suggestions, what should I do to match the outputs?
If only the last block of ECB / CBC padding is different then you can be pretty sure that a different block cipher padding is used. To validate which padding is used you can try (as Topaco did in the comments below the question) or you can decrypt the ciphertext without padding. For Java that would be "AES/CBC/NoPadding".
So if you do that given the key (and IV) then you will get the following output in hexadecimals:
73733D62726F636B2670773D3132333435362674733D3230313930333034323334343331000000000000000000000000
Clearly this is zero padding.
Zero padding has one big disadvantage: if your ciphertext ends with a byte valued zero then this byte may be seen as padding and stripped from the result. Generally this is not a problem for plaintext consisting of an ASCII or UTF-8 string, but it may be trickier for binary output. Of course, we'll assume here that the string doesn't use a null terminator that is expected to be present in the encrypted plaintext.
There is another, smaller disadvantage: if your plaintext is exactly the block size then zero padding is non-standard enough that there are two scenarios:
the padding is always applied and required to be removed, which means that if the plaintext size is exactly a number of times the block size that still a full block of padding is added (so for AES you'd have 1..16 zero valued bytes as padding);
the padding is only applied if strictly required, which means that no padding is applied if the plaintext size is exactly a number of times the block size (so for AES you'd have 0..15 zero valued bytes as padding).
So currently, for encryption, you might have to test which one is expected / accepted. E.g. Bouncy Castle - which is available for C# and Java - always (un)pads, while the horrid PHP / mcrypt library only pads where required.
You can always perform your own padding of course, and then use "NoPadding" for Java. Remember though that you never unpad more than 16 bytes.
General warning: encryption without authentication is unfit for transport mode security.
I'm tasked with building a consumer of an API that requires an encrypted token with a seed value that is the UNIX time. The example I was shown was implemented using Java which I'm unfamiliar with, and after reading through documentation and other stack articles have been unable to find a solution.
Using the javax.crypto.SecretKey, javax.crypto.SecretKeyFactory, javax.crypto.spec.PBEKeySpec, and javax.crypto.spec.SecretKeySpec protocols, I need to generate a token similar to the below:
public class EncryptionTokenDemo {
public static void main(String args[]) {
long millis = System.currentTimeMillis();
String time = String.valueOf(millis);
String secretKey = "somekeyvalue";
int iterations = 12345;
String iters = String.valueOf(iterations);
String strToEncrypt_acctnum = "somevalue|" + time + "|" + iterations;
try {
byte[] input = strToEncrypt_acctnum.toString().getBytes("utf-8");
byte[] salt = secretKey.getBytes("utf-8");
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
SecretKey tmp = factory.generateSecret(new PBEKeySpec(secretKey.toCharArray(), salt, iterations, 256));
SecretKeySpec skc = new SecretKeySpec(tmp.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skc);
byte[] cipherText = new byte[cipher.getOutputSize(input.length)];
int ctLength = cipher.update(input, 0, input.length, cipherText, 0);
ctLength += cipher.doFinal(cipherText, ctLength);
String query = Base64.encodeBase64URLSafeString(cipherText);
// String query = cipherText.toString();
System.out.println("The unix time in ms is :: " + time);
System.out.println("Encrypted Token is :: " + query);
} catch (Exception e) {
System.out.println("Error while encrypting :" + e);
}
}
}
Should I be using the built-in library hashlib to implement something like this? I can't really find documentation for implementing a PBKDF2 encryption with iterations/salt as inputs. Should I be using pbkdf2? Sorry for the vague questions, I'm unfamiliar with the encryption process and feel like even just knowing what the correct constructor would be is a step in the right direction.
Yes, the Python equivalent is hashlib.pbkdf2_hmac. For example this code:
from hashlib import pbkdf2_hmac
key = pbkdf2_hmac(
hash_name = 'sha1',
password = b"somekeyvalue",
salt = b"somekeyvalue",
iterations = 12345,
dklen = 32
)
print(key)
produces the same key as your Java code.
However, the problem with this code (as mentioned in memo's comment) is the use of salt. The salt should be random and unique for each password. You can create secure random bytes with os.urandom, so a better example would be:
from hashlib import pbkdf2_hmac
from os import urandom
salt = urandom(16)
key = pbkdf2_hmac('sha1', b"somekeyvalue", salt, 12345, 32)
You may also want to increase the number of iterations (I think the recommended minimum number is 10,000).
The rest of the code is easy to 'translate'.
For the timestamp, use time.time to get the current time and multiply by 1000.
import time
milliseconds = str(round(time.time() * 1000))
For encoding you can use base64.urlsafe_b64encode (it includes padding, but you could remove it with .rstrip(b'=')).
Now, for the encryption part, Python doesn't have a built-in encryption module, so you'll have to use a third party library. I recommend pycryptodome or cryptography.
At this point I must warn you that the AES mode you're using is very weak. Please consider using CBC or CTR, or better yet use an authenticated encryption algorithm.
I'm trying to reproduce an old encryption/decryption done in Java to a new one in Ruby one because I'm rebuilding the whole app. All this to change this encryption asap, obviously.
Here is the Java code:
public class MyClass {
private static String algo;
private static SecretKey key;
static {
algo = "AES";
String keyString = "someString";
byte[] decodedKey = Base64.getDecoder().decode(keyString);
key = new SecretKeySpec(decodedKey, 0, decodedKey.length, algo);
}
private static String decrypt(String encrypted) {
try {
Cipher cipher = Cipher.getInstance(algo);
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decodedBytes = Base64.getDecoder().decode(encrypted.getBytes());
byte[] original = cipher.doFinal(decodedBytes);
return new String(original);
}
catch (Exception e) {
// Some error
return "bad";
}
}
private static String encrypt(String toEncrypt) {
try {
Cipher cipher = Cipher.getInstance(algo);
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encrypted = cipher.doFinal(toEncrypt.getBytes());
byte[] encryptedValue = Base64.getEncoder().encode(encrypted);
return new String(encryptedValue);
}
catch (Exception e) {
// Some error
return "bad";
}
}
}
Java code comes from here
I have a problem with the decryption. Here is my Ruby code:
key = Digest::SHA256.digest(key)
aes = OpenSSL::Cipher.new('AES-256-CBC')
aes.decrypt
aes.key = Digest::SHA256.digest(key)
aes.update(secretdata) + aes.final
# => OpenSSL::Cipher::CipherError: bad decrypt
What am I doing wrong?
The "AES" algorithm description isn't complete; it will use a default mode of operation and padding scheme. In the provider included in the JRE this will default to ECB and PKCS#7 padding ("AES/ECB/PKCS5Padding"). This is obviously insecure as ECB is insecure, but due to applications relying on this default it cannot be changed (which is one of the reasons why having defaults was a mistake in the first place).
Furthermore, in the code you've provided there is no hashing involved. This is a good thing as a single secure hash over a key is not enough to provide a good amount of security. Storing a key in a string is almost as bad, but not quite. Instead however the key is base 64 encoded.
So you have to switch to 'AES-256-ECB' and remove the double hashing of the key, replacing it with base 64 decoding instead.
It was not that easy but here is my way to do it.
class ManualEncryption
class << self
attr_accessor :configuration
def config
#configuration ||= Configuration.new
end
def aes
return #aes if #aes
#aes = OpenSSL::Cipher.new(config.algo) # "AES-256-ECB"
#aes
end
def decodedKey
return #decodedKey if #decodedKey
#decodedKey = Base64.decode64(config.key_string) # "mySecretString"
end
def configure
yield(config)
raise 'Algo not specified' unless config.algo
raise 'key not specified' unless config.key_string
end
def encrypt(value)
raise 'No configuration done' unless config.algo && config.key_string
aes_perform('encrypt', value)
end
def decrypt(value)
raise 'No configuration done' unless config.algo && config.key_string
return value unless value
aes_perform('decrypt', value)
end
def aes_perform(status, value)
aes.reset
if status.eql?('encrypt')
aes.encrypt
aes.key = decodedKey
aes_val = aes.update(value) + aes.final
Base64::encode64(aes_val)
else
aes.decrypt
aes.key = decodedKey
decoded_value = Base64::decode64(value)
aes.update(decoded_value) + aes.final
end
end
end
class Configuration
attr_accessor :algo, :key_string
attr_reader :aes
end
end
Note: I still have a problem with the encryption. It creates \n inside my encrypted value and I don't know why. I'm working on it.
I'm only asking this because I have read many posts for 2 days now about crypto AES encryption, and just when I thought I was getting it, I realized I wasn't getting it at all.
This post is the closest one to my issue, I have exactly the same problem but it is unanswered:
CryptoJS AES encryption and JAVA AES decryption value mismatch
I have tried doing it in many ways but I haven't gotten it right.
First Off
I'm getting the already encrypted string (I only got the code to see how they were doing it), so modifying the encryption way is not an option. That's why all the similar questions aren't that useful to me.
Second
I do have access to the secret key and I can modify it (so adjusting length is an option if neccessary).
The encryption is done on CryptoJS and they send the encrypted string as a GET parameter.
GetParamsForAppUrl.prototype.generateUrlParams = function() {
const self = this;
return new Promise((resolve, reject) => {
const currentDateInMilliseconds = new Date().getTime();
const secret = tokenSecret.secret;
var encrypted = CryptoJS.AES.encrypt(self.authorization, secret);
encrypted = encrypted.toString();
self.urlParams = {
token: encrypted,
time: currentDateInMilliseconds
};
resolve();
});
};
I can easily decrypt this on javascript using CryptoJS with:
var decrypted = CryptoJS.AES.decrypt(encrypted_string, secret);
console.log(decrypted.toString(CryptoJS.enc.Utf8));
But I don't want to do this on Javascript, for security reasons, so I'm trying to decrypt this on Java:
String secret = "secret";
byte[] cipherText = encrypted_string.getBytes("UTF8");
SecretKey secKey = new SecretKeySpec(secret.getBytes(), "AES");
Cipher aesCipher = Cipher.getInstance("AES");
aesCipher.init(Cipher.DECRYPT_MODE, secKey);
byte[] bytePlainText = aesCipher.doFinal(byteCipherText);
String myDecryptedText = = new String(bytePlainText);
Before I had any idea of what I was doing, I tried base64 decoding, adding some IV and a lot of stuff I read, of course none of it worked.
But after I started to understand, kinda, what I was doing, I wrote that simple script above, and got me the same error on the post: Invalid AES key length
I don't know where to go from here. After reading a lot about this, the solution seems to be hashing or padding, but I have no control on the encryption method, so I can't really hash the secret or pad it.
But as I said, I can change the secret key so it can match some specific length, and I have tried changing it, but as I'm shooting in the dark here, I don't really know if this is the solution.
So, my question basically is, If I got the encrypted string (in javascript like the first script) and the secret key, is there a way to decrypt it (in Java)? If so, how to do it?
Disclaimer: Do not use encryption unless you understand encryption concepts including chaining mode, key derivation functions, IV and block size. And don't roll your own security scheme but stick to an established one. Just throwing in encryption algorithms doesn't mean an application has become any more secure.
CryptoJS implements the same key derivation function as OpenSSL and the same format to put the IV into the encrypted data. So all Java code that deals with OpenSSL encoded data applies.
Given the following Javascript code:
var text = "The quick brown fox jumps over the lazy dog. 👻 👻";
var secret = "René Über";
var encrypted = CryptoJS.AES.encrypt(text, secret);
encrypted = encrypted.toString();
console.log("Cipher text: " + encrypted);
We get the cipher text:
U2FsdGVkX1+tsmZvCEFa/iGeSA0K7gvgs9KXeZKwbCDNCs2zPo+BXjvKYLrJutMK+hxTwl/hyaQLOaD7LLIRo2I5fyeRMPnroo6k8N9uwKk=
On the Java side, we have
String secret = "René Über";
String cipherText = "U2FsdGVkX1+tsmZvCEFa/iGeSA0K7gvgs9KXeZKwbCDNCs2zPo+BXjvKYLrJutMK+hxTwl/hyaQLOaD7LLIRo2I5fyeRMPnroo6k8N9uwKk=";
byte[] cipherData = Base64.getDecoder().decode(cipherText);
byte[] saltData = Arrays.copyOfRange(cipherData, 8, 16);
MessageDigest md5 = MessageDigest.getInstance("MD5");
final byte[][] keyAndIV = GenerateKeyAndIV(32, 16, 1, saltData, secret.getBytes(StandardCharsets.UTF_8), md5);
SecretKeySpec key = new SecretKeySpec(keyAndIV[0], "AES");
IvParameterSpec iv = new IvParameterSpec(keyAndIV[1]);
byte[] encrypted = Arrays.copyOfRange(cipherData, 16, cipherData.length);
Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding");
aesCBC.init(Cipher.DECRYPT_MODE, key, iv);
byte[] decryptedData = aesCBC.doFinal(encrypted);
String decryptedText = new String(decryptedData, StandardCharsets.UTF_8);
System.out.println(decryptedText);
The result is:
The quick brown fox jumps over the lazy dog. 👻 👻
That's the text we started with. And emojis, accents and umlauts work as well.
GenerateKeyAndIV is a helper function that reimplements OpenSSL's key derivation function EVP_BytesToKey (see https://github.com/openssl/openssl/blob/master/crypto/evp/evp_key.c).
/**
* Generates a key and an initialization vector (IV) with the given salt and password.
* <p>
* This method is equivalent to OpenSSL's EVP_BytesToKey function
* (see https://github.com/openssl/openssl/blob/master/crypto/evp/evp_key.c).
* By default, OpenSSL uses a single iteration, MD5 as the algorithm and UTF-8 encoded password data.
* </p>
* #param keyLength the length of the generated key (in bytes)
* #param ivLength the length of the generated IV (in bytes)
* #param iterations the number of digestion rounds
* #param salt the salt data (8 bytes of data or <code>null</code>)
* #param password the password data (optional)
* #param md the message digest algorithm to use
* #return an two-element array with the generated key and IV
*/
public static byte[][] GenerateKeyAndIV(int keyLength, int ivLength, int iterations, byte[] salt, byte[] password, MessageDigest md) {
int digestLength = md.getDigestLength();
int requiredLength = (keyLength + ivLength + digestLength - 1) / digestLength * digestLength;
byte[] generatedData = new byte[requiredLength];
int generatedLength = 0;
try {
md.reset();
// Repeat process until sufficient data has been generated
while (generatedLength < keyLength + ivLength) {
// Digest data (last digest if available, password data, salt if available)
if (generatedLength > 0)
md.update(generatedData, generatedLength - digestLength, digestLength);
md.update(password);
if (salt != null)
md.update(salt, 0, 8);
md.digest(generatedData, generatedLength, digestLength);
// additional rounds
for (int i = 1; i < iterations; i++) {
md.update(generatedData, generatedLength, digestLength);
md.digest(generatedData, generatedLength, digestLength);
}
generatedLength += digestLength;
}
// Copy key and IV into separate byte arrays
byte[][] result = new byte[2][];
result[0] = Arrays.copyOfRange(generatedData, 0, keyLength);
if (ivLength > 0)
result[1] = Arrays.copyOfRange(generatedData, keyLength, keyLength + ivLength);
return result;
} catch (DigestException e) {
throw new RuntimeException(e);
} finally {
// Clean out temporary data
Arrays.fill(generatedData, (byte)0);
}
}
Note that you have to install the Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy. Otherwise, AES with key size of 256 won't work and throw an exception:
java.security.InvalidKeyException: Illegal key size
Update
I have replaced Ola Bini's Java code of EVP_BytesToKey, which I used in the first version of my answer, with a more idiomatic and easier to understand Java code (see above).
Also see How to decrypt file in Java encrypted with openssl command using AES?.
When encrypting on one system and decrypting on another you are at the mercy of system defaults. If any system defaults do not match (and they often don't) then your decryption will fail.
Everything has to be byte for byte the same on both sides. Effectively that means specifying everything on both sides rather than relying on defaults. You can only use defaults if you are using the same system at both ends. Even then, it is better to specify exactly.
Key, IV, encryption mode, padding and string to bytes conversion all need to be the same at both ends. It is especially worth checking that the key bytes are the same. If you are using a Key Derivation Function (KDF) to generate your key, then all the parameters for that need to be the same, and hence specified exactly.
Your "Invalid AES key length" may well indicate a problem with generating your key. You use getBytes(). That is probably an error. You need to specify what sort of bytes you are getting: ANSI, UTF-8, EBCDIC, whatever. The default assumption for the string to byte conversion is the likely cause of this problem. Specify the conversion to be used explicitly at both ends. That way you can be sure that they match.
Crypto is designed to fail if the parameters do not match exactly for encryption and decryption. For example, even a one bit difference in the key will cause it to fail.
I need to access some data that used PHP encryption. The PHP encryption is like this.
base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($cipher), $text, MCRYPT_MODE_ECB));
As value of $text they pass the time() function value which will be different each time that the method is called in. I have implemented this in Java. Like this,
public static String md5(String string) {
byte[] hash;
try {
hash = MessageDigest.getInstance("MD5").digest(string.getBytes("UTF-8"));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Huh, MD5 should be supported?", e);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Huh, UTF-8 should be supported?", e);
}
StringBuilder hex = new StringBuilder(hash.length * 2);
for (byte b : hash) {
int i = (b & 0xFF);
if (i < 0x10) hex.append('0');
hex.append(Integer.toHexString(i));
}
return hex.toString();
}
public static byte[] rijndael_256(String text, byte[] givenKey) throws DataLengthException, IllegalStateException, InvalidCipherTextException, IOException{
final int keysize;
if (givenKey.length <= 192 / Byte.SIZE) {
keysize = 192;
} else {
keysize = 256;
}
byte[] keyData = new byte[keysize / Byte.SIZE];
System.arraycopy(givenKey, 0, keyData, 0, Math.min(givenKey.length, keyData.length));
KeyParameter key = new KeyParameter(keyData);
BlockCipher rijndael = new RijndaelEngine(256);
ZeroBytePadding c = new ZeroBytePadding();
PaddedBufferedBlockCipher pbbc = new PaddedBufferedBlockCipher(rijndael, c);
pbbc.init(true, key);
byte[] plaintext = text.getBytes(Charset.forName("UTF8"));
byte[] ciphertext = new byte[pbbc.getOutputSize(plaintext.length)];
int offset = 0;
offset += pbbc.processBytes(plaintext, 0, plaintext.length, ciphertext, offset);
offset += pbbc.doFinal(ciphertext, offset);
return ciphertext;
}
public static String encrypt(String text, String secretKey) throws Exception {
byte[] givenKey = String.valueOf(md5(secretKey)).getBytes(Charset.forName("ASCII"));
byte[] encrypted = rijndael_256(text,givenKey);
return new String(Base64.encodeBase64(encrypted));
}
I have referred this answer when creating MCRYPT_RIJNDAEL_256 method."
Encryption in Android equivalent to php's MCRYPT_RIJNDAEL_256
"I have used apache codec for Base64.Here's how I call the encryption function,
long time= System.currentTimeMillis()/1000;
String encryptedTime = EncryptionUtils.encrypt(String.valueOf(time), secretkey);
The problem is sometimes the output is not similar to PHP but sometimes it works fine.
I think that my MCRYPT_RIJNDAEL_256 method is unreliable.
I want to know where I went wrong and find a reliable method so that I can always get similar encrypted string as to PHP.
The problem is likely to be the ZeroBytePadding. The one of Bouncy always adds/removes at least one byte with value zero (a la PKCS5Padding, 1 to 16 bytes of padding) but the one of PHP only pads until the first block boundary is encountered (0 to 15 bytes of padding). I've discussed this with David of the legion of Bouncy Castle, but the PHP zero byte padding is an extremely ill fit for the way Bouncy does padding, so currently you'll have to do this yourself, and use the cipher without padding.
Of course, as a real solution, rewrite the PHP part to use AES (MCRYPT_RIJNDAEL_128), CBC mode encryption, HMAC authentication, a real Password Based Key Derivation Function (PBKDF, e.g. PBKDF2 or bcrypt) and PKCS#7 compatible padding instead of this insecure, incompatible code. Alternatively, go for OpenSSL compatibility or a known secure container format.