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.
Related
I was given following java code:
private static String key = "0123456789ABCDEF0123456789ABCDEF"; // Not real key
public static String Encrypt(String text)
{
byte[] encrypted, bytekey = hexStringToByteArray(key);
SecretKeySpec sks = new SecretKeySpec(bytekey, "AES");
try
{
Cipher cipher = Cipher.getInstance("AES");
cipher.init(1, sks, cipher.getParameters());
encrypted = cipher.doFinal(text.getBytes());
}
catch (Exception e)
{
System.out.println("Error using AES encryption with this Java instance");
e.printStackTrace();
System.exit(1);
return null;
}
String encryptedText = byteArrayToHexString(encrypted);
return encryptedText;
}
Passing Password123 into this returns 6836A38816248A0C7DD89400A997251A. I'm not looking for comments on the security of this. I'm aware. I didn't write it, I just need to duplicate it.
I have to create C# code that has the same functionality. I have tried many code snippets from all over SO and other web sites. None of them produce the same output when given a specific input.
I added some debug statements to the java code to get the following information about the algorithm:
sks.getAlgorithm(): AES (duh)
sks.getFormat(): RAW
cipher.getAlgorithm(): AES (again, duh)
cipher.getBlockSize(): 16
cipher.getParameters(): null
cipher.getIV(): null (I think this might be my primary issue)
Here is one of the C# methods I found that looked promising:
private const string key = "0123456789ABCDEF0123456789ABCDEF"; // Not real key
private static byte[] encryptionKey= new byte[16];
static void SetupKey()
{
var secretKeyBytes = Encoding.UTF8.GetBytes(key);
Array.Copy(secretKeyBytes, encryptionKey, Math.Min(encryptionKey.Length, secretKeyBytes.Length));
}
public static String Encrypt3(String secret)
{
SetupKey();
byte[] inputBytes = UTF8Encoding.UTF8.GetBytes(secret);
using (MemoryStream ms = new MemoryStream())
{
using (AesManaged cryptor = new AesManaged())
{
cryptor.Mode = CipherMode.CBC;
cryptor.Padding = PaddingMode.PKCS7;
cryptor.KeySize = 128;
cryptor.BlockSize = 128;
using (CryptoStream cs = new CryptoStream(ms, cryptor.CreateEncryptor(encryptionKey, null), CryptoStreamMode.Write))
{
cs.Write(inputBytes, 0, inputBytes.Length);
}
byte[] encryptedContent = ms.ToArray();
byte[] result = new byte[encryptedContent.Length];
System.Buffer.BlockCopy(encryptedContent, 0, result, 0, encryptedContent.Length);
return ByteArrayToHexString(result);
}
}
}
Every time I run this code, I get a different result, even though I'm passing null into the Initialization Vector(IV) parameter of cryptor.CreateEncryptor(). Is the AesManaged object using an internal IV even though I told it to use null? If I try to set the IV to null, I get an error.
What do I need to do to get the C# code to consistently return the same result as the java code?
NOTE: Both methods use HexStringToByteArray and ByteArrayToHexString. The original author of the java code, for some reason, wrote his own byte/hex converters. I recreated them in C#, but they work just like the build in functions.
So here is a question.
I have an old code on a Windows system that just takes a short string and makes CryptEncrypt on it with a public RSA key.
The minimum working example is here (avoiding any checks to make it shorter. avoiding freeing as well)
std::string testData = "12345678";
HCRYPTPROV context;
CryptAcquireContext(&context, nullptr, nullptr, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT);
auto pubK = FromBase64(PublicKey);
CERT_PUBLIC_KEY_INFO *pubKeyInfo = nullptr;
DWORD keyLength = 0;
CryptDecodeObjectEx(X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO,
pubK.data(), pubK.size(),
CRYPT_ENCODE_ALLOC_FLAG, nullptr,
&pubKeyInfo, &keyLength);
HCRYPTKEY key;
CryptImportPublicKeyInfo(context, X509_ASN_ENCODING, pubKeyInfo, &key);
std::vector<std::uint8_t> result(testData.begin(), testData.end());
DWORD size = testData.size();
CryptEncrypt(key, NULL, true, 0, result.data(), &size, result.size());
result.resize(size);
size = testData.size();
CryptEncrypt(key, NULL, true, 0, result.data(), &size, result.size());
std::cout << ToBase64(result) << "\n";
The code works and returns a base64 encoded string. like lTq01sOcgDcgwtDaFFoHH/Qb6xLw0KU+/n/+3t0eEb8l4N69QGcaEWf1qso3a4qn7Y8StlXcfMe8uspNF/KDj6qQOMvCuM+uUl+tkLd5NXiESsjycgjyxAqdCIO71iTSmsYVcsS3fY/gtIbO4UAFnCRPOXoSyqWqpXW7IRtFzL2N3MxgIBlIMErNvNWs5HPA7xAY/XO6UpSMWsQO4ppccdeNLSZDPwOxohKD/BX5oDin81nFn7fvIZgghfH5knF1nezK8IGKl+vtbgrwlUUULp/wJ4POceyIn0HaZoVsaCu6xFJcUJGfBqSvm4GZqkp2MlGxBODku0OSgEfIDEGMTg==.
Then I have to decrypt this string with the private key on another side running java.
I use bouncycastle:
try {
Security.addProvider(new BouncyCastleProvider());
String value = "";
AsymmetricKeyParameter privateKey =
(AsymmetricKeyParameter) PrivateKeyFactory.createKey(Base64.getDecoder().decode(privateKeyValue));
AsymmetricBlockCipher e = new RSAEngine();
e = new org.bouncycastle.crypto.encodings.PKCS1Encoding(e);
e.init(false, privateKey);
byte[] messageBytes = Base64.getDecoder().decode(inputdata);
byte[] hexEncodedCipher = e.processBlock(messageBytes, 0, messageBytes.length);
value = new String(hexEncodedCipher);
System.out.println(value);
return value;
}
catch (Exception e) {
System.out.println(e);
}
And this code shows me the next error:
org.bouncycastle.crypto.DataLengthException: input too large for RSA cipher.
And I believe I'm missing something on the windows side, because if I use the same keys on the Java side and crypt the same data with the same public key, the decryption works as expected.
Here the keys, I generated with openssl for this question (RSA 2048):
Private:
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCeL7K++3+vl52Q
WFC6ThgvNPlhHLlP5ylIMi/7Gy60wcCHtx8R5Hzi2j7Kx/uBXyr7SCbePS7NtqHx
meVK3VhEvYHz2uYaUNf6GqJgNNjfRymnL5ll8K8lq1wym6A7KZ+L3qLHH1oI6GfW
+uf32nUfy1PQvsatPN7aiJ4ymiaJj2LcI6Bp78CINsu56Cx9QBN9uoXqKO/7NjOz
y2GRdYdckiqCkcmGDzA4/5tJROxj21gUbxwoUR+yz2sVFGlcJycsDDJiIXiPjbVN
XgM/YfmRjdAPQQwXXj9AK5w/dp9TPq621WqYES74rIY05cba4v1kihmFbs0DHLrV
fQZB0Pu9AgMBAAECggEAGuD//nO9vpiErYJUNVQPx/W4akf3NRySZzIf9QspZI2H
qYf0P5YTonhzMwHIOrNxGkGoWRsMWOgvnF4KGC6EUSniaw1HDDGwgU8FSFOyhj4R
VddAuZGsMTps8CyBjYwFED9EaZFqOxlCi8UWpYb5X+2s0EuadtVhCMEuIGsRIU53
mfW11182YtbAI7Zqi7wg/w0yRz5rVj4ph8nbSFPgi6qtVWA9bxVxNeRTXyQDWjUU
NFQzGcRG6D/SYTnRzTndVM5TmTwEVt0hvJNA2/pMWF5XMu6B2exOpJIVVVdQ40l/
XHh33SU8+gQYY56rHzFebCa0Nuxwdk0paluv+x3CAQKBgQDKwe8QXfNlMDqrM06n
iq/NwKE7tB+1gs1nKr4qZdbI71IzgQJgIJcxbJwbuE6z6Yk8PoancEELH9mSY7EN
YGeSO3QsO1LF1vyJWgWQU1G3pfC9sh8sY6f9WYg8+JogIeJsgjwf7PXrGaBq8++Q
GBMUATzPAh/ERIqUip0nEQ2IJwKBgQDHuYgPvv77qhLc+ZNOdhaZtibeVsFx50V+
a+qR+INSTJ/CChNkoVtMg803ZQWckJBOYL/TS2Mw+ctNomUg+ImjULGT9GzqMeme
HkPpAj5pNyNVpRfVNZfmS2cwwuRyE1QbFGs3C4p7D5MKKCfl4/8MBQXtJGWrQZFr
owh2NNyHewKBgHsMmSYorlcBnwlZOOnK7AiFWBRgq0G/4SI0OXaHmYMWYp+pMqTe
AoPXMyJLh0/+ce/izlt9b6vtp2AFKmVA1XpUpJtXYVN5tocw3+GH/zbh+SlWmT6a
OFAz7s953CeWCNDrdMu3RkNoqQdfhUrAoYtpeNr0ogy9wBCH0vnrinfPAoGBALNy
U/hpz+lH1qjqKFsPqKC001ljM21msL60sU4zrbHNHKEXsnLwsvodVc3Wm2MfVDjH
nrJ2gomnde2r4hbsl6W/w70+mHkXHWKuqK97D54zJzE1IyOygmctCmr6QIzqJuAp
yWbsnKCSzrcKe0aHQkmHXdrCoAJt5/2AvwKN3jJvAoGAIaLts1F7shIZ365FXyWD
poOFtXsNRXESkcYpJ2Pn+K0fUNtdH4BtR/5Z5bsEWXBf2LbL7s15UZFZbck0KH3K
22BRgPQze7wEhMVFlIMNhF17WrOh2NTGUAVz2CYSthbYh0QSI+5XJk0AEfiQcqYk
+E4bZw/RUTY94V+ITEK6F8g=
public
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAni+yvvt/r5edkFhQuk4Y
LzT5YRy5T+cpSDIv+xsutMHAh7cfEeR84to+ysf7gV8q+0gm3j0uzbah8ZnlSt1Y
RL2B89rmGlDX+hqiYDTY30cppy+ZZfCvJatcMpugOymfi96ixx9aCOhn1vrn99p1
H8tT0L7GrTze2oieMpomiY9i3COgae/AiDbLuegsfUATfbqF6ijv+zYzs8thkXWH
XJIqgpHJhg8wOP+bSUTsY9tYFG8cKFEfss9rFRRpXCcnLAwyYiF4j421TV4DP2H5
kY3QD0EMF14/QCucP3afUz6uttVqmBEu+KyGNOXG2uL9ZIoZhW7NAxy61X0GQdD7
vQIDAQAB
What do I wrong?
CryptEncrypt returns the ciphertext in little-endian format, see CryptEncrypt( Remarks, last section). For the decryption in Java the byte array messageBytes must therefore be inverted, e.g. here.
I have the following simple encryption code running in node.js:
var crypto = require('crypto');
var encKey = "FOO"; // Not the real key. Assume it works though.
var encrypt = function(str) {
var cipher = crypto.createCipher('aes-256-cbc', encKey);
var crypted = cipher.update(str, 'utf-8', 'hex');
crypted += cipher.final('hex');
return crypted;
};
Which I can also decrypt as below:
var crypto = require('crypto');
var encKey = "FOO"; // Not the real key. Assume it works though.
var decrypt = function(str) {
var decipher = crypto.createDecipher('aes-256-cbc', encKey);
var decrypted = decipher.update(str, 'hex', 'utf-8');
decrypted += decipher.final('utf-8');
return decrypted;
};
This all works fine. Strings are encrypting and decrypting as expected. But now I am faced with task of decrypting encrypted strings from this node.js code, in Java. And that is where things are going wrong and I am not sure why.
For decryption, My Java code looks like this:
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.util.Arrays;
private static final String encKey = "FOO";
private static SecretKeySpec secretKey;
private static byte[] key;
public static String decrypt(String str) throws Exception {
String hexDecodedStr = new String(Hex.decodeHex(str.toCharArray()));
setKey(encKey);
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return new String(cipher.doFinal(hexDecodedStr.getBytes()));
}
private static void setKey(String myKey) throws Exception {
MessageDigest sha = null;
try {
key = myKey.getBytes("UTF-8");
sha = MessageDigest.getInstance("SHA-1");
key = sha.digest(key);
key = Arrays.copyOf(key, 16);
secretKey = new SecretKeySpec(key, "AES");
}
catch (Exception e) {
throw e;
}
}
And it doesn't work. It seems like no matter what I try, I end up with some exception on the cipher.doFinal() call, or the String I get back is totally wrong. I know the node.js code is using aes-256-cbc, while the Java code is using AES/ECB/PKCS5Padding instead of AES/CBC/PKCS5Padding, but when I tried to use AES/CBC/PKCS5Padding, it was requiring an InitVector which I didn't have in node.js so I was unsure of how to proceed. Is node making an InitVector under the hood if not provided with one? Am I missing something totally obvious?
You seems to have the same issue as others OpenSSL encryption failing to decrypt C#
As far I understood the docs, the crypto libeary uses openssl. The openssl creates IV and key from the password using its EVP_BytesToKey function and random salt (not just hash). As dave pointed out, the crypto library uses no salt.
the output of openssl is Salted_{8 bytes salt}{ciphertext} so check what is output of the cipher ( I am unable to do it now)
I wrote a small article how to encrypt properly in Java
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.
I have an application developed on BlackBerry JDE 5.0.0 that encrypts a String using DES algorithm with ECB mode. After the encryption, the result is encoded by base64 encoding. But whenever I compare the result that i get from my encryption method with the result that i get on the online encryptor engine, it always give different result on the several last character. I tried to decrypt the result that i get form my encryption method with the online encriptor engine and it looks like the result is not the valid one. So how can I fix that different result on the several last character?
Here my encryption method code:
public String encryptDESECB(String text) throws MessageTooLongException
{
byte[] input = text.getBytes();
byte[] output = new byte[8];
byte[] uid = null;
uid = "431654625bd37673e3b00359676154074a04666a".getBytes();
DESKey key = new DESKey(uid);
try {
DESEncryptorEngine engine = new DESEncryptorEngine(key);
engine.encrypt(input, 0, output, 0);
String x= BasicAuth.encode(new String(output));
System.out.println("AFTER ENCODE"+x);
return new String(x);
} catch (CryptoTokenException e) {
return "NULL";
} catch (CryptoUnsupportedOperationException e) {
return "NULL";
}
}
The String that i want to encrypt is "00123456"
The Result that i get from my encryption method is:YnF2BWFV/8w=
The Result that i get from online encryptor engine (http://www.tools4noobs.com/online_tools/encrypt/) : YnF2BWFV9sw=
The Result that i get from android (With the same encryption algorithm & Method) : YnF2BWFV9sw=
Here's the code on Android:
public static String encryptDesECB(String data) {
try {
DESKeySpec keySpec = newDESKeySpec("431654625bd37673e3b00359676154074a04666a".getBytes("UTF8"));
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey key = keyFactory.generateSecret(keySpec);
// ENCODE plainTextPassword String
byte[] cleartext = data.getBytes("UTF8");
Cipher cipher = Cipher.getInstance("DES/ECB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key);
Logger.log(Log.INFO, new String(cipher.doFinal(cleartext)));
String encrypedPwd = Base64.encodeToString(cipher.doFinal(cleartext), Base64.DEFAULT);
Logger.log(Log.INFO, encrypedPwd);
return encrypedPwd;
} catch (Exception e) {
Logger.log(e);
return null;
}
}
Can anyone help me with this?
This is most likely caused by padding, as DES works with 8 byte blocks.
For more information check out this link:
http://www.tero.co.uk/des/explain.php#Padding
As long as you can properly decrypt the content you'll be fine.
I found my mistake. It turn out my BasicAuth Class isn't the correct one for encoding the encrypted string. Now I'm using the correct one Base64 Class for the encoding, and it turn out fine.