I have a file encrypted using the following code in c:
unsigned char ckey[] = "0123456789ABCDEF";
unsigned char iv[8] = {0};
AES_set_encrypt_key(ckey, 128, &key);
AES_ctr128_encrypt(indata, outdata, 16, &key, aesstate.ivec, aesstate.ecount, &aesstate.num);
I have to decrypt this file using java so I was using the code below to do it:
private static final byte[] encryptionKey = new byte[]{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F };
byte[] iv = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
IvParameterSpec ips = new IvParameterSpec(iv);
Cipher aesCipher = Cipher.getInstance("AES/CTR/NoPadding");
SecretKeySpec aeskeySpec = new SecretKeySpec(encryptionKey, "AES");
aesCipher.init(Cipher.DECRYPT_MODE, aeskeySpec, ips);
FileInputStream is = new FileInputStream(in);
CipherOutputStream os = new CipherOutputStream(new FileOutputStream(out), aesCipher);
copy(is, os);
os.close();
The JAVA code doesn't give me any error but the output is not correct.
What am I doing wrong?
My main doubts are if i'm using the correct padding (also tried PKCS5Padding without success) and if the key and iv are correct (don't know what the function AES_set_encrypt_key really does...).
** EDIT **
I think I have an answer to my own question, but I still have some doubts.
CTR means counter mode. The function AES_ctr128_encrypt receives as parameters the actual counter (ecount) and the number of blocks used (num).
The file is being encrypted in blocks of 16 bytes, like this:
for(int i = 0; i < length; i+=16)
{
// .. buffer processing here
init_ctr(&aesstate, iv); //Counter call
AES_ctr128_encrypt(indata, outdata, 16, &key, aesstate.ivec, aesstate.ecount, &aesstate.num);
}
the function init_ctr does this:
int init_ctr(struct ctr_state *state, const unsigned char iv[8])
{
state->num = 0;
memset(state->ecount, 0, 16);
memset(state->ivec + 8, 0, 8);
memcpy(state->ivec, iv, 8);
return 0;
}
This means that before every encryption/decryption the C code is resetting the counter and the ivec.
I am trying to decrypt the file as a whole in java. This probably means Java is using the counter correctly but the C code is not as it is resetting the counter at each block.
Is my investigation correct?
I have absolutely NO CONTROL over the C code that is calling openssl. Is there a way of doing the same in JAVA, i.e. resetting the counter at each block of 16? (The API only requests the key, algorithm, mode and IV)
My only other option is to use openssl via JNI but I was trying to avoid it...
Thank you!
I did not try it, but you should be able to effectively emulate what is done there on the C side - decrypt each 16-byte (=128 bit) block separately, and reset the cipher between two calls.
Please note that using CTR mode for just one block, with a zero initialization vector and counter, defeats the goal of CTR mode - it is worse than ECB.
If I see this right, you could try to encrypt some blocks of zeros with your C function (or the equivalent Java version) - these should come out as the same block each time. XOR this block with any ciphertext to get your plaintext back.
This is the equivalent to a Caesar cipher on a 128-bit alphabet (e.g. the 16-byte blocks), the block cipher adds no security here to a simple 128-bit XOR cipher. Guessing one block of plaintext (or more generally, guessing 128 bits at the right positions, not necessary all in the same block) allows getting the effective key, which allows getting all the remaining plaintext blocks.
Your encryption keys are different.
The C code uses the ASCII character codes for 0 through F, whereas the Javacode uses the actual bytes 0 through 16.
There are numerous serious problems with that C code:
As already noted, it is reinitialising the counter on every block. This makes the encryption completely insecure. This can be fixed by calling init_ctr() once only, prior to encrypting the first block.
It is setting the IV statically to zero. A fresh IV should be generated randomly, for example if (!RAND_bytes(iv, 8)) { /* handle error */ }.
The code appears to be directly using a password string as the key. Instead, a key should be generated from the password using a key derivation function like PBKDF2 (implemented in OpenSSL by PKCS5_PBKDF2_HMAC_SHA1()).
Related
I found on Google this code for encrypt/decrypt a string in Java:
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
byte[] input = "test".getBytes();
byte[] keyBytes = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17 };
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC");
System.out.println(new String(input));
// encryption pass
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] cipherText = new byte[cipher.getOutputSize(input.length)];
int ctLength = cipher.update(input, 0, input.length, cipherText, 0);
ctLength += cipher.doFinal(cipherText, ctLength);
System.out.println(new String(cipherText));
System.out.println(ctLength);
// decryption pass
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] plainText = new byte[cipher.getOutputSize(ctLength)];
int ptLength = cipher.update(cipherText, 0, ctLength, plainText, 0);
ptLength += cipher.doFinal(plainText, ptLength);
System.out.println(new String(plainText));
System.out.println(ptLength);
And this is the output (screenshot because I can't copy-paste some characters):
output screenshot
My question is:
Why the first input "test" is different from the second (decrypted) "test"?
I need this code to encrypt a password and save it on a TXT file and then read this encrypted password from the TXT file and decrypt it..
But if these two outputs are different I can't do this.
Second question:
Is it possible to exclude ";" from the encrypted text?
Can someone help me, please? Thanks!
If you read the documentation for getOutputSize() then you will see that it returns the maximum amount of plaintext to expect. The cipher instance cannot know how much padding is added, so it guesses high. You will have to resize the byte array when you are using ECB or CBC mode (or any other non-streaming mode).
System.out.println(ctLength);
As you can see, ctLength does have the correct size. Use Arrays.copyOf(plainText, ptLength) to get the right number of bytes, or use the four parameter String constructor (new String(plainText, 0, ptLength, StandardCharsets.UTF_8)) in case you're just interested in the string.
The ciphertext consists of random characters. It actually depends on your standard character set what you see on the screen. If you really need text, then you can base 64 encode the ciphertext.
ECB mode encryption is not suitable to encrypt strings. You should try and use a different mode that includes setting / storing an IV.
I'd use new String(StandardCharsets.UTF_8) and String#getBytes(StandardCharsets.UTF_8) to convert to and from strings. If you don't specify the character set then it uses the system default character set, and that means decrypting your passwords won't work on all systems. The allowed characters also differ with Linux & Android defaulting on UTF-8 while Java SE on Windows (still?) defaults to the Windows-1252 (extended Western-Latin) character set.
There is absolutely no need to use the Bouncy Castle provider for AES encryption (the compatible padding string is "PKCS5Padding").
Please don't grab random code samples from Google. You need to understand cryptography before you start implementing it. The chances that you grab a secure code sample is practically zero unfortunately.
I am using a sample code from a git repository to understand twofish algorithm, The code below works very fine the results are also correct checked from an online tool ref http://twofish.online-domain-tools.com/
the problem is as below :-
int[] plainText = new int[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C,
0x0D, 0x0E, 0x0F };
int[] p = new int[4];
for (int i = 0; i < 4; i++) {
p[i] = plainText[4 * i] + 256 * plainText[4 * i + 1] + (plainText[4 * i + 2] << 16)
+ (plainText[4 * i + 3] << 24);
}
System.out.println("Input:");
Utils.printInput(p);
int[] key = p; //
System.out.println("Key:");
Utils.printInput(key);
//
int[] encrypted = TwoFish.encrypt(p, key);
//
System.out.println("Encrypted:");
Utils.printInput(encrypted);
System.out.println();
int[] decrypted = TwoFish.decrypt(encrypted, key);
System.out.println("Decrypted:");
Utils.printInput(decrypted);
In the code above same key is used as plainText and Key, While there is a requirement of passing plain text and key
int[] encrypted = TwoFish.encrypt(p, key);
above code needs to take input from
int[] encrypted = TwoFish.encrypt("The plain text information", "000102030405060708090A0B0C0D0E0F");
OK, cryptography primer:
You need a mode of operation for the Twofish block cipher. I have trouble to recognize one you have in the code though, and that's not a good sign.
The mode of operation needs an IV, and a random - or at least a fully unpredictable IV - for CBC mode.
Your plaintext you need to encode. Using UTF-8 is recommended nowadays (it's compatible with ASCII, so for your string you really cannot go wrong).
You need a hexadecimal decoder to decode the key to a byte array.
By the way, generally we implement cryptographic block ciphers and other primitives to operate on bits - or more specifically bytes. The cipher or at least the mode of operation should accept bytes, not integers.
Good luck!
Quick one that's thus far been evading me (long night). I'm comparing AES256 in PHP vs Java and noticing discrepancies. Please for simplicity ignore the ascii key and the null IV, those will be replaced in production. But I need to get past this first and can't figure out where I am erring:
PHP:
echo base64_encode(
mcrypt_encrypt(
MCRYPT_RIJNDAEL_128,
"1234567890ABCDEF1234567890ABCDEF",
"This is a test",
MCRYPT_MODE_CBC,
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
)
);
Java
byte[] key = "1234567890ABCDEF1234567890ABCDEF".getBytes("UTF-8");
byte[] iv = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
AlgorithmParameterSpec ivSpec = new IvParameterSpec(iv);
SecretKeySpec newKey = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, newKey, ivSpec);
byte[] results = cipher.doFinal("This is a test".getBytes("UTF-8"));
return Base64.encodeToString(results,Base64.DEFAULT);
PHP output: 0KwK+eubMErzDaPU1+mwTQ==
Java output: DEKGJDo3JPtk48tPgCVN3Q==
Not quite what I was expecting o_O !
I've also tried MCRYPT_MODE_CBC, MCRYPT_MODE_CFB, MCRYPT_MODE_ECB, MCRYPT_MODE_NOFB, etc.. none of them produced the Java string.
PHP pads the input bytes with \0 to make it a multiple of the block size. The equivalent in Java would be this (assuming the string you want to encrypt is in data):
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
int blockSize = cipher.getBlockSize();
byte[] inputBytes = data.getBytes();
int byteLength = inputBytes.length;
if (byteLength % blockSize != 0) {
byteLength = byteLength + (blockSize - (byteLength % blockSize));
}
byte[] paddedBytes = new byte[byteLength];
System.arraycopy(inputBytes, 0, paddedBytes, 0, inputBytes.length);
cipher.init(Cipher.ENCRYPT_MODE, newKey, ivSpec);
byte[] results = cipher.doFinal(paddedBytes);
As a warning to this - zero-based padding is not desired. There's no way to determine the difference between \0 characters at the end of your string, and the actual padding. It's better to use PKCS5Padding instead, but you will get different results in PHP. Ask yourself if you NEED the encryption cross-platform like this.
I'm working on a project that requires 3DES encryption in Java. The issue is that I've been (and will continue to be) supplied with a 128-bit hex key like "0123456789ABCDEF0123456789ABCDEF". Conversion to bytes is no issue. What is the issue, however, is that the Java Cryptographic Extensions API will choke on this key, saying it is invalid. I gather that the MSB of each byte is merely a parity bit, so the JCE expects me to remove those (or so I think). In .NET, however, I can specify the key as supplied, and it quietly handles the encryption/decryption with no complaints.
Is there any way I can generate the kind of key the JCE expects from the kind of key I'm supplied?
I've found that the JCE allows you specify an 8-byte key for DES encryption, so I tried implementing 3DES as DES EDE using half of the supplied key. However, I'm still getting inconsistent results with .NET.
Here's the Java code:
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
public class Main{
public static void main(String[] args) throws Exception {
byte [] plain = "I eat fish every day".getBytes("utf-8");
byte [] keyBytes = new byte [] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00
};
byte [] key2Bytes = new byte [] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x0 }; // actual keys replaced with dummies.
SecretKey keySpec = new SecretKeySpec(keyBytes, "DES");
SecretKey keySpec2 = new SecretKeySpec(key2Bytes, "DES");
IvParameterSpec iv = new IvParameterSpec(new byte[8]);
Cipher e_cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
Cipher cipher = Cipher.getInstance("DES/CBC/NoPadding");
e_cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
cipher.init(Cipher.DECRYPT_MODE, keySpec2, iv);
byte [] cipherText = e_cipher.doFinal(plain);
cipherText = cipher.doFinal(cipherText);
cipherText = e_cipher.doFinal(cipherText);
System.out.println("Ciphertext: " + new sun.misc.BASE64Encoder().encode(cipherText));
}
}
and here's the .NET code:
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace EncryptionDemo
{
class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Hello World!");
// TODO: Implement Functionality Here
var plainBytes = Encoding.UTF8.GetBytes("I eat fish every day");
var keyBytes = new byte [] { 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00 };
var tripleDES = TripleDESCryptoServiceProvider.Create();
var transform = tripleDES.CreateEncryptor(keyBytes, new byte [8]);
var memStream = new MemoryStream();
var cStream = new CryptoStream(memStream, transform, CryptoStreamMode.Write);
cStream.Write(plainBytes, 0, plainBytes.Length);
cStream.FlushFinalBlock();
//memStream.Position = 0;
var cipherBytes = memStream.ToArray();
Console.WriteLine("Ciphertext: " + Convert.ToBase64String(cipherBytes));
Console.Write("Press any key to continue . . . ");
Console.ReadKey(true);
}
}
Both produce different outputs (some characters in the Base64 string are the same)
3DES keys are 192 bits long.
How are you creating the SecretKey instance? What error message to you get?
The Java code in your question is using DES, not "Triple DES". The algorithm name should be "DESede/CBC/PKCS5Padding". The code in your answer probably works because you got the algorithm right, not because you switched providers. The SunJCE provider in Java 6 will accept 128-bit keys (and use keying option 2). I am not sure about older versions.
the Sun provider doesn't accept 16-byte 3DES keys, but the BouncyCastle provider does. I just tried it out and it works like a charm - it produces the same output as the .NET code!
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
public class Main{
public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastleProvider());
byte [] plain = "I eat fish every day".getBytes("utf-8");
byte [] keyBytes = new byte [] { (byte) 0xC1, (byte) 0x57, (byte) 0x45, (byte) 0x08,
(byte) 0x85, (byte) 0x02, (byte) 0xB0, (byte) 0xD3,
(byte) 0xA2, (byte) 0xEF, (byte) 0x68, (byte) 0x43,
(byte) 0x5E, (byte) 0xE6, (byte) 0xD0, (byte) 0x75 };
SecretKey keySpec = new SecretKeySpec(keyBytes, "DESede");
IvParameterSpec iv = new IvParameterSpec(new byte[8]);
Cipher e_cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding", "BC");
e_cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
byte [] cipherText = e_cipher.doFinal(plain);
System.out.println("Ciphertext: " + new sun.misc.BASE64Encoder().encode(cipherText));
}
}
In the jPOS project, the problem is worked around by always using either single-length (8-byte) or triple-length (24-byte) keys. Let's say your clear double-length key (in bytes) is AAAAAAAA BBBBBBBB. All code in the jPOS project I've seen so far that uses the JCE appends the first 8 bytes again to the clear key, so it becomes a triple-length key as such: AAAAAAAA BBBBBBBB AAAAAAAA. It seems the Sun provider does accept this material for creating a SecreKeySpec, as it is 192 bits long, as #erickson mentioned.
I'm trying to create an application for Android that uses encryption to save user information and I cannot figure out what I'm doing wrong. I'm trying to create an instance of an AES cipher but the application keeps on throwing "InvalidKeyExceptions." Consider the following code:
public static final byte[] IV = new byte[]
{ 0x04, 0x08, 0x15, 0x16, 0x23, 0x42, 0x00, 0x00, 0x00, 0x00,0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
protected final IvParameterSpec params = new IvParameterSpec(IV);
protected Cipher myCipher;
public AESEncryptor(String passwd, InputStream source, String destinationFile)
{
try
{
myCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
Log.d("System.out.println", "Block Size: "+myCipher.getBlockSize());
myCipher.init(Cipher.ENCRYPT_MODE, AESEncryptor.generateSecretKeyFromPassword(passwd),params);
}
catch (Exception e)
{
e.printStackTrace();
}
}
I get this exception:
java.security.InvalidKeyException:
initialisation vector must be the same
length as block size..
The myCipher.init(...) line triggers this exception.
I understand what it's saying but according to myCipher.getBlockSize() the IV byte array should hold 16 bytes, and it does, but it doesn't work. I have also tried byte arrays of length 0-128, and nothing in that range works either.
Oh also, if I take this code, unaltered, and add it to a regular Java application, I get no errors. Compiling for Android seems to be causing this error.
Please help.
Thanks,
Ryan
Have you tried specifying explicitly the block size in your mode parameter?
Ex:
Cipher.getInstance("AES/CBC16/PKCS5Padding");
I noticed here that if you don't specify the block size then it is provider dependent.