I am encrypting my message with a constant secret key and random IV using AES/CTR/NoPadding in Java.
I plan to prepend the IV to the cipher text and then Base64 encode it.
Now, there is one problem that is bothering me. There can be multiple IV + Cipher text combinations that can result in my original message. Isn't that a problem? Also, is it safe to send IV as such (i.e. prepend/append to the cipher text) or there is some procedure that I should follow?
I am relatively new to cryptography, so pardon me if it's very simple question. I couldn't find any satisfying answer to this.
EDIT:
public static String encrypt(String message) {
try {
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
byte[] iv = generateRandomIV();
cipher.init(Cipher.ENCRYPT_MODE, SECRET_KEY, new IvParameterSpec(iv));
byte[] cipherText = cipher.doFinal(message.getBytes("utf-8"));
return DatatypeConverter.printBase64Binary(concat(iv, cipherText));
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static String decrypt(String encryptedMessage) {
try {
byte[] bytes = DatatypeConverter.parseBase64Binary(encryptedMessage);
byte[] iv = getIV(bytes);
byte[] cipherText = getCipherText(bytes);
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, SECRET_KEY, new IvParameterSpec(iv));
return new String(cipher.doFinal(cipherText));
} catch (Exception ex) {
ex.printStackTrace();
return encryptedMessage;
}
}
private static byte[] getIV(byte[] bytes) {
return Arrays.copyOfRange(bytes, 0, 16);
}
private static byte[] getCipherText(byte[] bytes) {
return Arrays.copyOfRange(bytes, 16, bytes.length);
}
and then
public static void main(String[] args) {
System.out.println(decrypt("wVroKV1UnL2NXiImS83hLKpLLJKk"));
System.out.println(decrypt("Q0tWAMZDhqMo0LbtEY7lF9D8Dkor"));
}
Both these produce same output -- "goody"
There can be multiple IV + Cipher text combinations that can result in my original message. Isn't that a problem?
The reason that you're using different IV's is that you can send the same message twice using the same key, with different ciphertext.
If it would generate the same ciphertext an adversary would know that the same message was send, leaking information about the message. So the idea of the IV is that it generates a different ciphertext and in most cases that is beneficial rather than a problem.
If it is a problem depends on your protocol. Note that the ciphertext length may still show information about the plaintext (i.e. "Affirmative, Sergeant" will of course be different than the encryption of "No").
You'll need an authentication tag / MAC value to protect against change of the message. Furthermore, you may want to include something like a message sequence number to make sure that replay attacks don't happen.
However, the deeper you go the more complex encryption becomes. If you require secure transport then in the end it is infinitely easier to use TLS or SSH (or any other applicable transport layer security).
Also, is it safe to send IV as such (i.e. prepend/append to the cipher text) or there is some procedure that I should follow?
Prepending it ("prepend" is not a word in many dictionaries, you could use "prefix" as well) is a common way of handling the IV, yes. The IV may be sent anyway and it doesn't need to be kept confidential. In the case of CBC however it must be random, not a sequence number.
Related
I have done Encryption with ,
public static String encrypt(String plainText) {
try {
byte[] keyData = secret_key.getBytes();
SecretKeySpec secretKey = new SecretKeySpec(keyData, "AES/ECB/PKCS7Padding");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] cipherText = cipher.doFinal(plainText.getBytes("UTF-8"));
String encryptedString = Base64.encodeToString(cipherText, Base64.NO_WRAP);
return encryptedString;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
it's working well.
but part of Decryption gives Error like,
W/System.err: javax.crypto.BadPaddingException: pad block corrupted
W/System.err: at com.android.org.bouncycastle.jce.provider.JCEBlockCipher.engineDoFinal(JCEBlockCipher.java:701)
W/System.err: at javax.crypto.Cipher.doFinal(Cipher.java:1111)
decrypt Code like,
public static String decrypt(String encryptedText) {
try {
byte[] keyData = secret_key.getBytes();
SecretKeySpec secretKey = new SecretKeySpec(keyData, "AES/ECB/PKCS7Padding");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] cipherText = Base64.decode(encryptedText,Base64.NO_WRAP);
String decryptedString = new String(cipher.doFinal(cipherText),"UTF-8");
return decryptedString;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
here what is the problem? How can i solve this Issue?
It is likely that your secret_key value contains bytes which are not well represented in the ambiguous encoding you're using. When you call String#getBytes() without specifying an encoding, you get the system default, which can vary.
You should use hexadecimal encoding whenever you represent your key as a String. This will be consistent across serialization/deserialization on every platform. There are many standard implementations of this encoding/decoding process available (i.e. org.bouncycastle.util.encoders.Hex.decode("0123456789ABCDEFFEDCBA9876543210"); or org.apache.commons.codec.binary.Hex.decodeHex("0123456789ABCDEFFEDCBA9876543210".toCharArray()); which both return the raw byte[]).
Some side notes:
You are using ECB mode of operation, which is extremely susceptible to frequency analysis for cryptanalysis and is effectively deprecated aside from toy crypto demonstrations. I suggest you use CBC, CTR, or GCM.
You do not provide an initialization vector (IV), so the same message encrypted with the same key will always yield identical cipher text. Use a unique and non-predictable IV for every encryption operation by generating 16 bytes from SecureRandom and populating it into an IvParameterSpec. You can prepend the IV bytes to the cipher text and transport/store it in the clear.
Your cipher text is not authenticated, allowing for malicious users to both manipulate encrypted data and to attempt decryption via padding oracle/CCA attacks. Use an authenticated encryption with associated data (AEAD) mode like GCM, or use an HMAC/SHA-256 message authentication code (MAC) over the cipher text, and verify it using a constant-time equals method before attempting any decryption.
You do not need to provide the mode of operation or padding scheme when instantiating a key. SecretKey key = new SecretKeySpec(keyData, "AES"); is sufficient.
I would like to encrypt a String with RSA encryption. My public/private keys were generated and stored in DB. In android, I use this code:
public static String encryptRSAToString(String text, String strPublicKey) {
byte[] cipherText = null;
String strEncryInfoData="";
try {
KeyFactory keyFac = KeyFactory.getInstance("RSA");
KeySpec keySpec = new X509EncodedKeySpec(Base64.decode(strPublicKey.trim().getBytes(), Base64.DEFAULT));
Key publicKey = keyFac.generatePublic(keySpec);
// get an RSA cipher object and print the provider
final Cipher cipher = Cipher.getInstance("RSA");
// encrypt the plain text using the public key
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
cipherText = cipher.doFinal(text.getBytes());
strEncryInfoData = new String(Base64.encode(cipherText,Base64.DEFAULT));
} catch (Exception e) {
e.printStackTrace();
}
return strEncryInfoData.replaceAll("(\\r|\\n)", "");
}
For debug purpose, I try to call 2 times this method with the same parameters and String result were similar (as expected).
I want to generate the same encrypted String in java. However, "android.util.Base64" class is not available in Java, so I've tried with the default Base64 class:
public static String encryptRSAToString(String text, String strPublicKey) {
byte[] cipherText = null;
String strEncryInfoData="";
try {
KeyFactory keyFac = KeyFactory.getInstance("RSA");
KeySpec keySpec = new X509EncodedKeySpec(Base64.decodeBase64(strPublicKey.trim().getBytes()));
Key publicKey = keyFac.generatePublic(keySpec);
// get an RSA cipher object and print the provider
final Cipher cipher = Cipher.getInstance("RSA");
// encrypt the plain text using the public key
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
cipherText = cipher.doFinal(text.getBytes());
strEncryInfoData = new String(Base64.encodeBase64(cipherText));
} catch (Exception e) {
e.printStackTrace();
}
return strEncryInfoData.replaceAll("(\\r|\\n)", "");
}
But the String generated in Android and the one in java are different.
Generated in Android side :
Ky2T4j1JdI081ZESVJgxZXEf/xmtpehfv/EwpVvKQxUu1JI8lwXP2Rc66jHZRc0P846ZYuF3C9YEmWoKbXGXk2MBuT5KVxa2yoTbwZlMmhVOX3X3Efq0VyaO5zZ4qavIq036cA3MzvQbUAb678UdbALW/CjRCsOdeH+hSCzNQ+0=
Generated in JAVA side :
XhSLxfiJUUdZW5kWh0MEPSrqoROBBhNC/krfTx+sdnXML3WegYbMzSvNnPgB8+8Z9joEUBMmoeBI1OhTF6qPFL1EEixkFYAkGaryEFxvN/aFI75kEUj71OHNzAHAuvS+h+9Nssx9psSZ5gc2OoLQH0QtbGDyXB4p+qUGFCde4tY=
Does someone know how to solve my issue ?
thank you
It looks like you've been undone by relying on defaults. Never do that if you hope for interoperability.
Here are the two examples of mistakenly relying on defaults in your code that I've found.
final Cipher cipher = Cipher.getInstance("RSA");
The tranformation string is supposed to be of the form "algorithm/mode/padding" but you've left off the mode and padding specifications. As a result you got default values for those. The defaults are evidently different on Android and Oracle Java. You should always fully specify the transformation, for example:
final Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING");
Another bad example is
cipherText = cipher.doFinal(text.getBytes());
In text.getBytes() you are relying on the no-args getBytes() method which uses the default charset for the platform. But this default charset differs on different platforms, and thus this is not portable. In almost all cases I've run across you should specify the UTF-8 charset. So the correct line would thus be
cipherText = cipher.doFinal(text.getBytes("UTF-8"));
and the correct string constructor to use to recreate the original string in the decrypt method is the String(byte [] data, String charsetName).
I canĀ“t comment yet so I answer.
It is possible that different default configurations are being used. Check this question: Is there any difference between Apache's Base64.encodeBase64 and Android's Base64.encode with Base64.Default flag?
There are deviations of different cipher and hash implementations. I would suggest using OpenSSL as a common implementation.
I was wondering, is there any difference, if I init AES cipher, with and without IvParameterSpec?
With IvParameterSpec
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16]));
Without IvParameterSpec
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
I tested with some sample test data, their encryption and decryption result yield the same.
However, since I'm not the security expert, I don't want to miss out anything, and create a potential security loop hole. I was wondering, which is the correct way?
A bit of background (I'm sorry if you already know this, it's just worth making sure we're using the same terminology):
AES is a block cipher, an encryption algorithm that operates on 128-bit blocks.
CBC is a block cipher mode, a way of using a block cipher to encrypt large amounts of data.
Block cipher modes need an initialisation vector (IV), which is a block of initialisation data, usually the same size as the block size of the underlying cipher.
(The Wikipedia on block cipher modes - http://en.wikipedia.org/wiki/Block_cipher_mode - is really good, and makes it clear why you need an IV.)
Different block modes impose different requirements on the IV selection process, but they all have one thing in common:
You must never encrypt two different messages with the same IV and key.
If you do, an attacker can usually get your plaintext, and sometimes your key (or equivalently useful data).
CBC imposes an additional constraint, which is that the IV must be unpredictable to an attacker - so artjom-b's suggestion of using a SecureRandom to generate it is a good one.
Additionally, as artjob-b points out, CBC only gives you confidentiality. What that means in practice is that your data is kept secret, but there's no guarantee that it arrives in one piece. Ideally, you should use an authenticated mode, such as GCM, CCM, or EAX.
Using one of these modes is a really, really good idea. Encrypt-then-MAC is unwieldy even for the experts; avoid it if you can. (If you have to do it, remember that you must use different keys for encryption and MAC.)
By default when you encrypt - your cipher will generate a random IV. You must use exactly that specific IV when you decrypt that data.
The good news is that IV is not a secret thing - you can store it in public. The main idea is to keep it different for every encrypt-decrypt operation.
Most of the times you will need to encrypt-decrypt various data and storing each IV for each piece of data is a pain.
That's why IV is often stored along with the encrypted data in a single string, as a fixed size prefix.
So that when you decrypt your string - you definitely know that first 16 bytes (in my case) are your IV, the rest of the bytes - are the encrypted data and you need to decrypt it.
Your payload (to store or send) will have the following structure:
[{IV fixed length not encrypted}{encrypted data with secret key}]
Let me share my encrypt and decrypt methods, I'm using AES, 256 bit secret key, 16 bit IV, CBC MODE and PKCS7Padding.
As Justin King-Lacroix stated above you better use GCM, CCM, or EAX block modes. Do not use ECB!
Result of encrypt() method is safe & ready to store in DB or send anywhere.
Note a comment where you can use custom IV - just replace new SecureRandom() with new IvParameterSpec(getIV()) (you can input there your static IV but this is strongly NOT recommended)
private Key secretAes256Key is a class field with a secret key, it is initialized in the constructor.
private static final String AES_TRANSFORMATION_MODE = "AES/CBC/PKCS7Padding"
the encrypt() method:
public String encrypt(String data) {
String encryptedText = "";
if (data == null || secretAes256Key == null)
return encryptedText;
}
try {
Cipher encryptCipher = Cipher.getInstance(AES_TRANSFORMATION_MODE);
encryptCipher.init(Cipher.ENCRYPT_MODE, secretAes256Key, new SecureRandom());//new IvParameterSpec(getIV()) - if you want custom IV
//encrypted data:
byte[] encryptedBytes = encryptCipher.doFinal(data.getBytes("UTF-8"));
//take IV from this cipher
byte[] iv = encryptCipher.getIV();
//append Initiation Vector as a prefix to use it during decryption:
byte[] combinedPayload = new byte[iv.length + encryptedBytes.length];
//populate payload with prefix IV and encrypted data
System.arraycopy(iv, 0, combinedPayload, 0, iv.length);
System.arraycopy(encryptedBytes, 0, combinedPayload, iv.length, encryptedBytes.length);
encryptedText = Base64.encodeToString(combinedPayload, Base64.DEFAULT);
} catch (NoSuchAlgorithmException | BadPaddingException | NoSuchPaddingException | IllegalBlockSizeException | UnsupportedEncodingException | InvalidKeyException e) {
e.printStackTrace();
}
return encryptedText;
}
And here is the decrypt() method:
public String decrypt(String encryptedString) {
String decryptedText = "";
if (encryptedString == null || secretAes256Key == null)
return decryptedText;
}
try {
//separate prefix with IV from the rest of encrypted data
byte[] encryptedPayload = Base64.decode(encryptedString, Base64.DEFAULT);
byte[] iv = new byte[16];
byte[] encryptedBytes = new byte[encryptedPayload.length - iv.length];
//populate iv with bytes:
System.arraycopy(encryptedPayload, 0, iv, 0, 16);
//populate encryptedBytes with bytes:
System.arraycopy(encryptedPayload, iv.length, encryptedBytes, 0, encryptedBytes.length);
Cipher decryptCipher = Cipher.getInstance(AES_TRANSFORMATION_MODE);
decryptCipher.init(Cipher.DECRYPT_MODE, secretAes256Key, new IvParameterSpec(iv));
byte[] decryptedBytes = decryptCipher.doFinal(encryptedBytes);
decryptedText = new String(decryptedBytes);
} catch (NoSuchAlgorithmException | BadPaddingException | NoSuchPaddingException | IllegalBlockSizeException | InvalidAlgorithmParameterException | InvalidKeyException e) {
e.printStackTrace();
}
return decryptedText;
}
Hope this helps.
When no IvParameterSpec is provided then the Cipher should initialize a random IV itself, but it seems that in your case, it doesn't do this (new byte[16] is an array filled with 0x00 bytes). It seems the Cipher implementation is broken. In that case you should always provide a new random IV (necessary for semantic security).
This is usually done this way:
SecureRandom r = new SecureRandom(); // should be the best PRNG
byte[] iv = new byte[16];
r.nextBytes(iv);
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(iv));
When you then send or store the ciphertext, you should prepend the IV to it. During decryption you only need to slice the IV off the front of the ciphertext to use it. It doesn't need to be kept secret, but it should be unique.
Note that CBC mode alone only gives you confidentiality. If any type of manipulation of ciphertexts (malicious or non-malicious) is possible then you should use an authenticated mode like GCM or EAX. Those will also give you integrity in addition to confidentiality. If you don't have access to those (SpongyCastle has them), you could use a message authentication code (MAC) in an encrypt-then-MAC scheme, but it is much harder to implement correctly.
I am trying to encrypt in client and decrypt in sever using AES,
so using cryptojs to encrypt in client side with CBC mode and nopadding
in server side also using Cipher class with same mode and nopadding
function call()
{
var key = CryptoJS.enc.Hex.parse('roshanmathew1989');
var iv = CryptoJS.enc.Hex.parse('roshanmathew1989');
var encrypted = CryptoJS.AES.encrypt("roshanmathew1989",key,{ iv: iv},
{padding:CryptoJS.pad.NoPadding});
alert(encrypted.ciphertext.toString(CryptoJS.enc.Base64));
alert(encrypted.iv.toString());
}
Server side code
public class Crypto
{
private static byte[] key = null;
public void setKey(String key){this.key=key.getBytes();}
public String encrypt(String strToEncrypt)
{
String encryptedString =null;
try
{
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
final SecretKeySpec secretKey = new SecretKeySpec(key,"AES");
System.out.println("sdfsdf = "+key.toString());
IvParameterSpec ips = new IvParameterSpec(key);
cipher.init(Cipher.ENCRYPT_MODE, secretKey,ips);
encryptedString = Base64.encodeBase64String(cipher.doFinal(strToEncrypt.getBytes()));
}
catch(Exception e)
{
System.out.println(" ERROR : "+e.getMessage());
}
return encryptedString;
} other method omitted ....
implementation
Crypto cry=new Crypto();
cry.setKey("roshanmathew1989");
String s=cry.encrypt("roshanmathew1989");
Results
Browser side value = O64X/bKNBu7R2Tuq2lUbXeFlQ7wD2YnFasyyhsVUryw=
Server side value of s = RrNcVIER/75fzdjHr884sw==
Can anybody point out the mistake?
There are a few things wrong with the code:
you are using hexadecimal decoding of the key in JavaScript, and String.getBytes() - character encoding without specifying the character set - in Java
your key is 16 characters (it should be 16, 24 or 32 randomized bytes), but it is not in hexadecimals
you are encrypting instead of decrypting on the "server side", although that one is probably on purpose
Take another good look on how to perform encoding and character-encoding, they are essential for good crypto and often performed incorrectly (it's probably the most common issue on Stackoverflow regarding encryption)
I am trying to encrypt and decrypt a message as mentioned in the below code. Basically I want to encrypt a message with a public key and convert that encrypted message from byte array to String. And decrypt this string into original text. Here are the both methods. Here encryption works fine but decryption fails (error is "Data must start with zero"). I think this is causing because I convert encrypted byte array into String.
How do I solve this? (I want to have encrypted byte array as string and use it for decryption) Is there any other approach (with public and private keys)
public static String getEncryptedMessage(String publicKeyFilePath,
String plainMessage) {
byte[] encryptedBytes;
try {
Cipher cipher = Cipher.getInstance("RSA");
byte[] publicKeyContentsAsByteArray = getBytesFromFile(publicKeyFilePath);
PublicKey publicKey = getPublicKey(publicKeyContentsAsByteArray);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
encryptedBytes = cipher.doFinal(plainMessage.getBytes());
return new String(encryptedBytes);
} catch (Throwable t) {
}
}
public static String getDecryptedMessage(
String privateKeyFilePath, String encryptedMessage)
{
byte[] decryptedMessage;
try {
Cipher cipher = Cipher.getInstance("RSA");
byte[] privateKeyContentsAsByteArray = getBytesFromFile(privateKeyFilePath);
PrivateKey privateKey = getPrivateKey(privateKeyContentsAsByteArray);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
decryptedMessage = cipher.doFinal(encryptedMessage.getBytes());
return new String(decryptedMessage);
} catch (Throwable t) {
}
If you look at this page (http://www.wikijava.org/wiki/Secret_Key_Cryptography_Tutorial) you will need to do base-64 encoding to turn the bytes into a string, then to decrypt it you would just decode it then decrypt.
Base-64 encoding uses the first 7 bits of a byte, to make something that is printable or emailable, for example.
UPDATE:
I made a mistake, there are 64 characters that it would be encoded in, again, in order to make it easier to use as something printable.
Why don't you treat the message as byte array from encryption to decryption? Why changing it to String in the middle? (I know it seems like a question, but it's actually an answer...)
Using RSA directly on unformatted data may leave your application vulnerable to an adaptive chosen ciphertext attack. For details please see Chapter 8, pages 288-289, of the Handbook of Applied Cryptography, a freely-available book from CRC Press. (It's well worth buying the bound edition, if you're really interested in cryptography -- you'll be stunned at the quality for the price.)
Because of this attack, most protocols that integrate RSA use RSA for encrypting randomly-generated session keys or signing hash functions with outputs that ought to be indistinguishable from random, OR using very carefully formatted messages that will fail to be correctly interpreted. (See Note 8.63 in HAC for details.)