I want to encrypt data in BlackBerry using the AES 256 encryption method. The requirement is to encrypt with No Padding; "AES/ECB/NoPadding". I am passing a 16 byte array and the encrypted data returned is a hex value of length 32. I have tried the following but it is not producing the correct result. The returned value is different from the expected encrypted value; tested in Android. The results between Android and BlackBerry do not tally. I have used the following method:
public static String EncryptData(byte[] keyData, byte[] data) throws Exception {
String encryptedData = "";
AESKey key = new AESKey(keyData);
NoCopyByteArrayOutputStream out = new NoCopyByteArrayOutputStream();
AESEncryptorEngine engine = new AESEncryptorEngine(key);
BlockEncryptor encryptor = new BlockEncryptor(engine, out);
encryptor.write(data, 0, data.length);
int finalLength = out.size();
byte[] cbytes = new byte[finalLength];
System.arraycopy(out.getByteArray(), 0, cbytes, 0, finalLength);
encryptedData = getHexString(cbytes);
return encryptedData;
}
Can anyone please guide?
EDIT: Below is the equivalent Android code:
Dim Kg As KeyGenerator
Dim c As Cipher
c.Initialize("AES/ECB/NoPadding") ' just "DES" actually performs "DES/ECB/PKCS5Padding".
Kg.Initialize("DESede")
Kg.KeyFromBytes(key)
bytes = Kg.KeyToBytes
msg_data = c.Encrypt(msg_data, Kg.key, False)
Return Bconv.HexFromBytes(msg_data)
There's a mistake in your Basic4Android code. You initialize the cipher with AES:
c.Initialize("AES/ECB/NoPadding")
but then initialize the key generator with TripleDES:
Kg.Initialize("DESede")
According to this documentation, just change "DESede" to "AES":
Kg.Initialize("AES")
Also, I wouldn't recommend using AES with ECB and no padding. It's insecure, especially when it's just as easy to use CBC or CTR mode. See this wikipedia article for an example of how unsafe it really is.
Related
Code in angular using crypto-js:
let key = '12345123451234512345123451234509';// actual keys are different and has same length of 32 char
let iv = '12345123451234512345123451234509';
let secret_key = CryptoJS.enc.Hex.parse(key);
let secret_iv = CryptoJS.enc.Hex.parse(iv);
let encryptedString = CryptoJS.AES.encrypt(
JSON.stringify(data),
secret_key,
{
iv: secret_iv,
padding: CryptoJS.pad.ZeroPadding
}
).toString();
let requestObj = {
input: encryptedString.trim()
}
I am not able to do same encryption in android.
Android Code
String key32Char = "12345123451234512345123451234509";
String iv32Char = "12345123451234512345123451234509";
byte[] srcBuff = jsonString.getBytes("UTF-8");
//SecretKeySpec secretKeySpec = new SecretKeySpec(key32Char.getBytes(), "AES");
//IvParameterSpec ivParameterSpec = new IvParameterSpec(iv32Char.getBytes());
SecretKeySpec secretKeySpec = new SecretKeySpec(Base64.decode(key32Char, Base64.NO_WRAP), "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(Base64.decode(iv32Char, Base64.NO_WRAP));
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] dstBuff = cipher.doFinal(srcBuff);
String encryptedString = Base64.encodeToString(dstBuff, Base64.NO_WRAP);
JSONObject requestObj = new JSONObject();
requestObj.put("input", encryptedString);
What CryptoJS.enc.Hex.parse(key) line does ?
How to do same encryption ?
IV and Key:To match the key and IV part both must use either base64 or hex decodings.
In Hex encoded string there are 32 hex char that makes 128-bit. However, the same string can be rejected by base64 decode and if not rejected the output will not be 128-bit. You need to use
byte[] bytes = new BigInteger("7F" + str, 16).toByteArray();
SecretKeySpec key = new SecretKeySpec(bytes, 1, bytes.length-1, "AES");
to convert the hex string into byte array.
padding: CryptoJS.pad.ZeroPadding is useful if your data size is an exact multiple of 128. Otherwise, you need to use this parameter to say that I'll use this for testing my new padding scheme. You need to better use Pkcs7 that was the default.
In Java you need getInstance("AES/CBC/PKCS5Padding");
Mode of operation: The default in JS is CBC, therefor you need the same in Java, as above getInstance("AES/CBC/PKCS5Padding");
Output: To compare the outputs you need to see the same result. In Java you convert the output into base64, so you need the same for JS.
As you can see, you must do the same steps, parameters for both.
Note that: CBC mode is archaic and you should prefer authenticated encryption modes like AES-GCM or ChaCha20-Poly1305. They not only provides confidentiality but also integrity and authentication. Unfortunately, crypto-js doesn't have them. But you can use some other JS libraries for that.
As name suggests, CryptoJS.enc.Hex.parse(key) parses a Hex String and uses it as key. So you need to do the same for your java code.
In addition, you need to select correct encryption mode and padding too. Your CryptoJs code uses CBC mode so you need to do same in Java Code. Your are using zero padding in CryptoJs side which is not available in java, so you need to do it manually. But in general, using zero padding is a bad idea and it is better to use PKCS5 padding for example which is default for CryptoJs.
With these things, these 2 codes match:
let key = '12345123451234512345123451234509';// actual keys are different and has same length of 32 char
let iv = '12345123451234512345123451234509';
let secret_key = CryptoJS.enc.Hex.parse(key);
let secret_iv = CryptoJS.enc.Hex.parse(iv);
let encryptedString = CryptoJS.AES.encrypt(
"0123456789012345x",
secret_key,
{
iv: secret_iv,
}
).toString();
let requestObj = {
input: encryptedString.trim()
}
Java:
public void doit()
{
byte[] key32Char = hexStringToByteArray("12345123451234512345123451234509");
byte[] iv32Char = hexStringToByteArray("12345123451234512345123451234509");
byte[] srcBuff = "0123456789012345x".getBytes();
SecretKeySpec secretKeySpec = new SecretKeySpec(key32Char, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv32Char);
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] dstBuff = cipher.doFinal(srcBuff);
String encryptedString = new String(Base64.getEncoder().encode(dstBuff));
System.out.print(encryptedString);
}
catch(Exception e) {
System.out.print(e.toString());
return;
}
}
public 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;
}
Update:
If you are forced to use bad idea of zero padding, you need to keep real size of data and do padding manually:
public void doitZeroPadding()
{
...
// For the simplicity, I assume that data size is smaller than 128.
// You need to change this part as needed.
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
int dsize = srcBuff.length + 1; // 1 is for plain buffer size
// This line align size to the multiple of block size.
int newBufSize = ((dsize + cipher.getBlockSize() - 1) / cipher.getBlockSize()) * cipher.getBlockSize();
byte[] newSrcBuf = new byte[newBufSize];
// You need real buffer size, or you don't know how long is decrypted buffer.
// I add it inside encrypting buffer to prevent other to see real decrypted buffer size.
// But if you want to have exact same encrypted buffer on both sides, you must remove it.
newSrcBuf[0] = (byte)(srcBuff.length);
System.arraycopy(srcBuff, 0, newSrcBuf, 1, srcBuff.length);
// Now use newSrcBuf/newBufSize
...
}
on the decryption side, check real size from decrypted buffer and use that size starting byte 1 for creating string.
I am trying to encrypt data using AES but I dont know why the output is larger than the input.
I used this function to derive the key
public byte[] deriveKey(String p, byte[] s, int i, int l) throws Exception {
PBEKeySpec ks = new PBEKeySpec(p.toCharArray(), s, i, l);
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
return skf.generateSecret(ks).getEncoded();
}
After that I send the data which is (112 bytes) and the key to the encrypt function but I get encrypted data (154 bytes)
public String encrypt(String s,byte[] d) throws Exception {
// Perform Encryption
SecretKeySpec eks = new SecretKeySpec(d, "AES");
Cipher c = Cipher.getInstance("AES/CTR/NoPadding");
c.init(Cipher.ENCRYPT_MODE, eks, new IvParameterSpec(new byte[16]));
byte[] es = c.doFinal(s.getBytes(StandardCharsets.UTF_8));
}
The plaintext that you want to encrypt is a string (s) which you encode to a byte array right before encryption: s.getBytes(StandardCharsets.UTF_8).
If the plaintext string contains non-ASCII characters (code points 128 and up), those will be encoded as two or more bytes with UTF-8 (see the table in the Wikipedia article). English text will likely consist of the same number of bytes as characters. Other languages might not be so lucky and their encoding from string to binary data will be blown up.
CTR mode is a streaming mode of operation, so the plaintext/ciphertext input will always be the same size as the ciphertext/plaintext output. The problem is of course that a scheme like AES-CTR has three inputs: key, data and an IV/nonce.
Only if you're changing the password/key every time you encrypt, using a static zero-byte IV will be somewhat secure. If you reuse the password/key even once, you'll run into the two-time pad (many-time pad) problem where an attacker who simply observes ciphertexts might deduce the plaintexts just by looking at them (nice example).
If you cannot guarantee the single use of password/key, then you must use a new IV every time you encrypt. No more new IvParameterSpec(new byte[16]), but something like
SecureRandom r = new SecureRandom();
byte[] iv = new byte[16];
r.nextBytes(iv);
Arrays.fill(iv, 12, 16, (byte)0); // zero out the counter part
In many encryption algorithms, padding is inevitable. That's why you see a size increase on cipher. This post might be helpful to you to understand what happens: https://security.stackexchange.com/questions/29993/aes-cbc-padding-when-the-message-length-is-a-multiple-of-the-block-size
In android i get always IllegalBlockSizeException, the data are encrypted in nodejs server and looks like (node.js: encrypting data that needs to be decrypted?):
var crypto = require('crypto');
console.log(crypto.getCiphers(), crypto.getHashes());
var algorithm = 'aes128'; // or any other algorithm supported by OpenSSL
var key = 'password';
var cipher = crypto.createCipher(algorithm, key);
var encrypted = cipher.update(data, 'utf8', 'binary') + cipher.final('binary');
fs.writeFile(file, encrypted, function (err) {
cb(err);
});
android code:
private static byte[] decrypt(byte[] raw, byte[] encrypted) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] decrypted = cipher.doFinal(encrypted);
return decrypted;
}
call method from file is in input stream (is):
byte [] b = new byte[2000000];
is.read(b, 0, 2000000);
byte[] decryptedData = decrypt(key,"password".getBytes());
result = new String(decryptedData, "UTF8").split("\n");
android code is inspired by : android encryption/decryption with AES where i dont use part of SecretKey with SecureRandom... which is for sure wrong, but i dont use any secure random in node.js part. The problem can be also with coding data in file.
I generaly generate a file in nodejs which is downloaded by app and stored in sdcard i'm not sure if i should be realy care about these data but will be cool have it crypted, isn't it?:-)
Thank you so much for any help or advice;-)
An IllegalBlockSizeException means that your input is not a multiple of the AES block size (16 bytes).
Your use of the decryption method looks completely wrong:
byte [] b = new byte[2000000];
is.read(b, 0, 2000000);
byte[] decryptedData = decrypt(key,"password".getBytes()); // <--- ???!
You are passing an eight byte constant value for your ciphertext. Instead, you should be passing the data you read from your input stream.
I would strongly recommend you research the correct way to read an entire input stream, because this code snippet suggests you are not handling resources correctly. You are also likely to end up with a byte array much larger than your actual data (unless your file is exactly 2000000 bytes long).
Side note: always specify the mode and padding when creating a Cipher object. For instance, if you know your JavaScript code uses CBC-mode and PKCS#7 padding, select:
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
This is important, because otherwise you are relying on default values that may differ between platforms.
I am trying to implement AES128 algorithm on Android, and I have referenced this link for a basic AES implementation (http://java.sun.com/developer/technicalArticles/Security/AES/AES_v1.html).
The problem is,for my project the key is predefined, and it is 36 bytes, not 16/24/32 bytes. So I always got a "key length not 128/194/256 bits" exception. I try the solution from iphone sdk(see this link: The iOS encryption framework) and it works even when I pass a 36 byte predefined key. As I can not find the implementation details for the BlockCipher.c/CommonCryptor.c released by Apple, Can any body help me figure out how they select 16 bytes from 36 bytes?
Thanks.
-----------------------------------update Sep 13th------------------------------------
In order to avoid confusion I provide some sample and my progress. I change some data that is confidential, but the length and format remain the same. And for saving time I only reveal the core functions. No comments for the code as I think the code is self-explained enough.
the iOS sample:
NSString * _key = #"some 36 byte key";
StringEncryption *crypto = [[[StringEncryption alloc] init] autorelease];
NSData *_inputData = [inputString dataUsingEncoding:NSUTF8StringEncoding];
CCOptions padding = kCCOptionPKCS7Padding;
NSData *encryptedData = [crypto encrypt:_inputData key:[_key dataUsingEncoding:NSUTF8StringEncoding] padding:&padding];
NSString *encryptedString = [encryptedData base64EncodingWithLineLength:0];
return encryptedString;
the [crypto encrypt] implementation is exactly the same as the link I mentioned above. It calls the doCipher in encryption mode. The core functions includes CCCryptorCreate, CCCryptorUpdate and CCCryptorFinal, which are from . The CCCryptorCreate deals with the key length. It passes the raw key bytes, and pass an integer 16 (kCCKeySizeAES128) as the key size and do the trick. The call hierarchy is like CCCryptorCreate/CommonCryptor.c => ccBlockCipherCallouts->CCBlockCipherInit/BlockCipher.c => ccAlgInfo->setkey/BlockCipher.c . setkey is actually a pointer to a function, for AES it points to aes_cc_set_key. And I can not find the aes_cc_set_key implementation, got lost here.
----------------------------------------Update Sep 13th -----------------------------
I change the _key in iOS sample code, manually taking the first 16 byte as the new key, other parts remain the same, and it is working!!! Up to this point I solve the key length problem.
But the Android version outputs different from the iOS version for some long plain text, like 30 or 40 bytes. my java implementation is like below:
String key = "some 16 byte key";
byte[] keyBytes = key.getBytes("UTF-8");
byte[] plainBytes = plainText.getBytes("UTF-8");
SecretKeySpec skeySpec = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(plainBytes);
String result = Base64.encodeBytes(encrypted);
return result;
Base64 is from org.apache.commons.codec.binary.Base64. What is the problem? or any hints on c/c++ libraries that can do the same thing? I can import it into android as well.
The remaining difference (provided that you only used the first 16 bytes of the key) is the cipher streaming mode. The iOS code uses CBC mode with an initialization set to all zeros. The Android code however uses ECB.
So the correct Java/Android code is:
// convert key to bytes
byte[] keyBytes = key.getBytes("UTF-8");
// Use the first 16 bytes (or even less if key is shorter)
byte[] keyBytes16 = new byte[16];
System.arraycopy(keyBytes, 0, keyBytes16, 0, Math.min(keyBytes.length, 16));
// convert plain text to bytes
byte[] plainBytes = plainText.getBytes("UTF-8");
// setup cipher
SecretKeySpec skeySpec = new SecretKeySpec(keyBytes16, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = new byte[16]; // initialization vector with all 0
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(iv));
// encrypt
byte[] encrypted = cipher.doFinal(plainBytes);
I have tested it with about 100 bytes of data and got exactly the same result on iOS and in Java.
There is no such thing as a 36-byte (288 bits) AES key. AES 256 would use a 32 byte key, so maybe that is what you have, with some additional header/trailer bytes. Where did you get this key from? What is the format? The Apple implementation may be throwing away the unneeded bytes, or it already knows about that special format you are using.
Is the 36 bytes actually a passphrase? If so, then it is likely that the key being used is SHA-256(passphrase) or SHA-512(passphrase).
ETA:
Re your update. I note that your code is using ECB mode. That is insecure. It may well be that Apple is using CBC mode, hence you difficulty in decrypting longer (more than 16 bytes) messages. Try changing the mode to CBC and using 16 more bytes of your mysterious input as the IV. Looking quickly at the Apple code for CommonCryptor.c, they appear to be using PKCS7 padding, so you should use that as well.
In case you want to apply base64 encoding for transporting over the network this is the right code:
public String encryptString(String string, String key)
{
byte[] aesData;
String base64="";
try
{
aesData = encrypt(key, string.getBytes("UTF8"));
base64 = Base64.encodeToString(aesData, Base64.DEFAULT);
}
catch (Exception e)
{
e.printStackTrace();
}
return base64;
}
public String decryptString(String string, String key)
{
byte[] debase64 = null;
String result="";
try
{
debase64=Base64.decode(string, Base64.DEFAULT);
byte[] aesDecrypted = decrypt(key, debase64);;
result = new String(aesDecrypted, "UTF8");
}
catch (Exception e)
{
e.printStackTrace();
}
return result;
}
private byte[] decrypt(String k, byte[] plainBytes) throws Exception
{
// convert key to bytes
byte[] keyBytes = k.getBytes("UTF-8");
// Use the first 16 bytes (or even less if key is shorter)
byte[] keyBytes16 = new byte[16];
System.arraycopy(keyBytes, 0, keyBytes16, 0, Math.min(keyBytes.length, 16));
// setup cipher
SecretKeySpec skeySpec = new SecretKeySpec(keyBytes16, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = new byte[16]; // initialization vector with all 0
cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(iv));
// encrypt
byte[] encrypted = cipher.doFinal(plainBytes);
return encrypted;
}
private byte[] encrypt(String k, byte[] plainBytes) throws Exception
{
// convert key to bytes
byte[] keyBytes = k.getBytes("UTF-8");
// Use the first 16 bytes (or even less if key is shorter)
byte[] keyBytes16 = new byte[16];
System.arraycopy(keyBytes, 0, keyBytes16, 0, Math.min(keyBytes.length, 16));
// setup cipher
SecretKeySpec skeySpec = new SecretKeySpec(keyBytes16, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = new byte[16]; // initialization vector with all 0
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(iv));
// encrypt
byte[] encrypted = cipher.doFinal(plainBytes);
return encrypted;
}
The situation:
I want an application that encrypts an string using RSA. I have the public key stored in res/raw, and as the key is 1024 bits, the resulting string has to be 128 bytes long. However, the resulting string after encrypting is 124 long, and as a result, the decryption crashes.
The function I am using to recover the public key is:
private PublicKey getPublicKey() throws Exception {
InputStream is = getResources().openRawResource(R.raw.publickey);
DataInputStream dis = new DataInputStream(is);
byte [] keyBytes = new byte [(int) is.available()];
dis.readFully(keyBytes);
dis.close();
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePublic(spec);
}
And the code of the function that I am using to encrypt:
private String rsaEncrypt (String plain) {
byte [] encryptedBytes;
Cipher cipher = Cipher.getInstance("RSA");
PublicKey publicKey = getPublicKey();
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
encryptedBytes = cipher.doFinal(plain.getBytes());
String encrypted = new String(encryptedBytes);
return encrypted;
}
P.D.: The code works perfectly in a desktop application, it just crashes in Android.
I really would appreciate any help,
Thank you very much.
String encrypted = new String(encryptedBytes);
is a bug. The output from crypto transforms are binary bytes. You cannot reliably store them as Strings.
Using is.available() is probably also a bug, but I'm not sure in this case.
Finally, it is one of my pet peeves when folks use the default charset versions of new String(...) and String.getBytes(). It is very rarely the right thing to do, especially in that Java claims to be "write once, run everywhere". The default charset is different on different platforms, which will trigger a bug in your code even if you do everything else correct. You should always specify a particular charset. In every case I have ever seen, simply using the UTF-8 Charset (Charset.forName("UTF-8");) will always work and represent data efficiently.