I've following aes encryption code in Java which I want to write it in C#, but it is not giving same output.
Java Code
public String doEncryptString(String salt, String password,String token) throws CryptoException {
try {
Cipher cipher = Cipher.getInstance("AES");
SecretKeySpec secretKeySpec = generateKeySpec(salt,password);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] inputBytes = token.getBytes();
byte[] outputBytes = cipher.doFinal(inputBytes);
return Base64Utils.encodeToString(outputBytes);
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | BadPaddingException
| IllegalBlockSizeException ex) {
throw new CryptoException("Error encrypting password", ex);
}
}
private SecretKeySpec generateKeySpec(String salt,String password) throws CryptoException{
try {
String generatedkey=salt+password;
byte[] key = generatedkey.getBytes("UTF-8");
MessageDigest sha = MessageDigest.getInstance("SHA-1");
key = sha.digest(key);
key = Arrays.copyOf(key, 16); // use only first 128 bit
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
return secretKeySpec;
} catch (NoSuchAlgorithmException | IOException ex) {
throw new CryptoException("Error encrypting password", ex);
}
}
This is what I've tried in C#
public static string DoEncrypt(string salt, string password, string token)
{
var tdes = new AesManaged();
tdes.Key = GenerateKey(salt, password);
tdes.Mode = CipherMode.ECB;
tdes.Padding = PaddingMode.PKCS7;
ICryptoTransform crypt = tdes.CreateEncryptor();
byte[] plain = Encoding.UTF8.GetBytes(token);
byte[] cipher = crypt.TransformFinalBlock(plain, 0, plain.Length);
return Convert.ToBase64String(cipher);
}
private static byte[] GenerateKey(string salt, string password)
{
string generatedkey = $"{salt}{password}";
var key = Encoding.UTF8.GetBytes(generatedkey);
var sha1 = SHA1Managed.Create();
key = sha1.ComputeHash(key);
return key.Take(16).ToArray(); // use only first 128 bit
}
string/token to encrypt : ZHKRIWB310XVVWG315PI7UZZWU1V0YYL5WE9JL
Java output: eUjNH8kcgWtlEmuCFHMPwnCFWjy5Pye/gF+itrPs1g8AjtAEZQqlzW/v7kEt2haG
My C# code output: O8sKdJWH+XCOIbexZPEwN5NxWqpWRHC5b3ZsihT8cfBqpI1eVr3PEr9Eq39a5pMn
I don't know what I am doing wrong here. Any help would be appreciated. Thanks
Update
My apologies everyone. The code translated in C# in working fine. By mistake, I was passing different salt value. Thanks everyone.
What's in TRANSFORMATION from the Java code?
You need also to use the same mode and padding to get the same results, meaning ECB and PKCS7 in your case.
Java seems to offer only PKCS5 padding? But it seems to be compatible with PKCS7? I'm not a Java dev and can't provide details, but there is a discussion here: https://crypto.stackexchange.com/questions/9043/what-is-the-difference-between-pkcs5-padding-and-pkcs7-padding where they say:
Some cryptographic libraries such as the SUN provider in Java indicate
PKCS#5 where PKCS#7 should be used - "PKCS5Padding" should have been
"PKCS7Padding". This is - with high probability - a legacy from the
time that only 8 byte block ciphers such as (triple) DES symmetric
cipher were available.
And by the way: for production never use ECB mode as it's unsafe.
I am using AESCrypt (gradle :compile 'com.scottyab:aescrypt:0.0.1')
to encrypt and decrypt the data.
TextView tv=(TextView)findViewById(R.id.demotext);
String encrypted="",decrypted="";
try {
encrypted = AESCrypt.encrypt("password","This is the best thing to go by");
decrypted = AESCrypt.decrypt("password",encrypted);
} catch (GeneralSecurityException e) {
e.printStackTrace();
}
System.out.println("EncryptedData:"+encrypted);
System.out.println("DecryptedData:"+decrypted);
tv.setText("Encrypted:"+encrypted +"\n"+"Decrypted:"+decrypted);
The code works perfectly fine in this case, I get the same input as decrypted text.
But, when I try to use already encrypted string using the same method (AES) from the site http://aesencryption.net/ as shown in the screenshot:
And copy paste that encrypted text like:
decrypted = AESCrypt.decrypt("password","sttA+FbNm3RkTovjHI8CcAdStXiMl45s29Jqle+y+pA=");
And then run the code then I get error saying :
javax.crypto.BadPaddingException: error:1e06b065:Cipher functions:EVP_DecryptFinal_ex:BAD_DECRYPT
But when I use the decrypted text into the same site it works fine as shown in the screenshot below.
private static byte[] decrypt(byte[] raw, byte[] encrypted) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] decrypted = cipher.doFinal(encrypted);
return decrypted;
}
Probably due to the algorithm to convert the passphrase 'password' to SecretKeySpec
This is the algorithm in AESCrypt
private static SecretKeySpec GenerateKey (final String password) throws NoSuchAlgorithmException, UnsupportedEncodingException {
final MessageDigest digest = MessageDigest.getInstance (HASH_ALGORITHM);
byte [] bytes = password.getBytes ("UTF-8");
digest.update (bytes, 0, bytes.length);
byte [] key = digest.digest ();
log ("SHA-256 key" key);
SecretKeySpec secretKeySpec = new SecretKeySpec (key, "AES");
secretKeySpec return;
}
And this is the (Java) example aesencryption.net
sha = MessageDigest.getInstance ("SHA-1");
key = sha.digest (key);
key = Arrays.copyOf (key, 16); // Use only first 128 bit
SecretKey = new SecretKeySpec (key, "AES");
The first one applies SHA256 hashing, and the second SHA-1 after completing up to 16 bytes, so the key is different.
I think you are encrypting and decrypting AES in the right way. You do not need to change anything.
But if you want to be compatible with aesencryption.net, you need to implement the same key generation algorithm. The code is not too good. I try to summarize
//Code from aesencryption.net
// Generate key
MessageDigest sha = null;
key = myKey.getBytes ("UTF-8");
sha = MessageDigest.getInstance ("SHA-1");
key = sha.digest (key);
key = Arrays.copyOf (key, 16); // Use only first 128 bit
SecretKey = new SecretKeySpec (key, "AES");
public static String encrypt (String strToEncrypt) {
Cipher cipher = Cipher.getInstance ("AES / ECB / PKCS5Padding");
cipher.init (Cipher.ENCRYPT_MODE, SecretKey);
Base64.encodeBase64String return (cipher.doFinal (strToEncrypt.getBytes ("UTF-8"))));
}
public static String decrypt (String strToDecrypt) {
Cipher cipher = Cipher.getInstance ("AES / ECB / PKCS5PADDING");
cipher.init (Cipher.DECRYPT_MODE, SecretKey);
return new String (cipher.doFinal (Base64.decodeBase64 (strToDecrypt))));
}
I can also provide my own code extracted from an Android app witch requires to store private user data. Data is ciphered with an AES key protected with an user passphrase
public static String SIMMETRICAL_ALGORITHM = "AES";
//Generate cipher key with user provided password
private static String getPassphraseSize16(String key) {
if (TextUtils.isEmpty(key)) {
return null;
}
char controlChar = '\u0014';
String key16 = key + controlChar;
if (key16.length() < 16) {
while (key16.length() < 16) {
key16 += key + controlChar;
}
}
if (key16.length() > 16) {
key16 = key16.substring(key16.length() - 16, key16.length());
}
return key16;
}
//AES cipher with passphrase
public static byte[] encrypt(byte[] message, String passphrase)
throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
String passphrase16 = getPassphraseSize16(passphrase);
SecretKeySpec secretKey = new SecretKeySpec(passphrase16.getBytes(), SIMMETRICAL_ALGORITHM);
Cipher cipher = Cipher.getInstance(SIMMETRICAL_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] encoded = cipher.doFinal(message);
return encoded;
}
//AES decipher with passphrase
public static byte[] decrypt(byte[] encodedMessage, String key) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
String passphrase16 = getPassphraseSize16(key);
SecretKeySpec secretKey = new SecretKeySpec(passphrase16.getBytes(), SIMMETRICAL_ALGORITHM);
Cipher cipher = Cipher.getInstance(SIMMETRICAL_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte decoded[] = cipher.doFinal(encodedMessage);
return decoded;
}
What's wrong with the following example?
The problem is that the first part of the decrypted string is nonsense. However, the rest is fine, I get...
Result: `£eB6O�geS��i are you? Have a nice day.
#Test
public void testEncrypt() {
try {
String s = "Hello there. How are you? Have a nice day.";
// Generate key
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
SecretKey aesKey = kgen.generateKey();
// Encrypt cipher
Cipher encryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
encryptCipher.init(Cipher.ENCRYPT_MODE, aesKey);
// Encrypt
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, encryptCipher);
cipherOutputStream.write(s.getBytes());
cipherOutputStream.flush();
cipherOutputStream.close();
byte[] encryptedBytes = outputStream.toByteArray();
// Decrypt cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);
// Decrypt
outputStream = new ByteArrayOutputStream();
ByteArrayInputStream inStream = new ByteArrayInputStream(encryptedBytes);
CipherInputStream cipherInputStream = new CipherInputStream(inStream, decryptCipher);
byte[] buf = new byte[1024];
int bytesRead;
while ((bytesRead = cipherInputStream.read(buf)) >= 0) {
outputStream.write(buf, 0, bytesRead);
}
System.out.println("Result: " + new String(outputStream.toByteArray()));
}
catch (Exception ex) {
ex.printStackTrace();
}
}
Lot of people including myself face lot of issues in making this work due to missing some information like, forgetting to convert to Base64, initialization vectors, character set, etc. So I thought of making a fully functional code.
Hope this will be useful to you all:
To compile you need additional Apache Commons Codec jar, which is available here:
http://commons.apache.org/proper/commons-codec/download_codec.cgi
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
public class Encryptor {
public static String encrypt(String key, String initVector, String value) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
System.out.println("encrypted string: "
+ Base64.encodeBase64String(encrypted));
return Base64.encodeBase64String(encrypted);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static String decrypt(String key, String initVector, String encrypted) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted));
return new String(original);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static void main(String[] args) {
String key = "Bar12345Bar12345"; // 128 bit key
String initVector = "RandomInitVector"; // 16 bytes IV
System.out.println(decrypt(key, initVector,
encrypt(key, initVector, "Hello World")));
}
}
In this answer I choose to approach the "Simple Java AES encrypt/decrypt example" main theme and not the specific debugging question because I think this will profit most readers.
This is a simple summary of my blog post about AES encryption in Java so I recommend reading through it before implementing anything. I will however still provide a simple example to use and give some pointers what to watch out for.
In this example I will choose to use authenticated encryption with Galois/Counter Mode or GCM mode. The reason is that in most case you want integrity and authenticity in combination with confidentiality (read more in the blog).
AES-GCM Encryption/Decryption Tutorial
Here are the steps required to encrypt/decrypt with AES-GCM with the Java Cryptography Architecture (JCA). Do not mix with other examples, as subtle differences may make your code utterly insecure.
1. Create Key
As it depends on your use-case, I will assume the simplest case: a random secret key.
SecureRandom secureRandom = new SecureRandom();
byte[] key = new byte[16];
secureRandom.nextBytes(key);
SecretKey secretKey = SecretKeySpec(key, "AES");
Important:
always use a strong pseudorandom number generator like SecureRandom
use 16 byte / 128 bit long key (or more - but more is seldom needed)
if you want a key derived from a user password, look into a password hash function (or KDF) with stretching property like PBKDF2 or bcrypt
if you want a key derived from other sources, use a proper key derivation function (KDF) like HKDF (Java implementation here). Do not use simple cryptographic hashes for that (like SHA-256).
2. Create the Initialization Vector
An initialization vector (IV) is used so that the same secret key will create different cipher texts.
byte[] iv = new byte[12]; //NEVER REUSE THIS IV WITH SAME KEY
secureRandom.nextBytes(iv);
Important:
never reuse the same IV with the same key (very important in GCM/CTR mode)
the IV must be unique (ie. use random IV or a counter)
the IV is not required to be secret
always use a strong pseudorandom number generator like SecureRandom
12 byte IV is the correct choice for AES-GCM mode
3. Encrypt with IV and Key
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv); //128 bit auth tag length
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
byte[] cipherText = cipher.doFinal(plainText);
Important:
use 16 byte / 128 bit authentication tag (used to verify integrity/authenticity)
the authentication tag will be automatically appended to the cipher text (in the JCA implementation)
since GCM behaves like a stream cipher, no padding is required
use CipherInputStream when encrypting large chunks of data
want additional (non-secret) data checked if it was changed? You may want to use associated data with cipher.updateAAD(associatedData); More here.
3. Serialize to Single Message
Just append IV and ciphertext. As stated above, the IV doesn't need to be secret.
ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + cipherText.length);
byteBuffer.put(iv);
byteBuffer.put(cipherText);
byte[] cipherMessage = byteBuffer.array();
Optionally encode with Base64 if you need a string representation. Either use Android's or Java 8's built-in implementation (do not use Apache Commons Codec - it's an awful implementation). Encoding is used to "convert" byte arrays to string representation to make it ASCII safe e.g.:
String base64CipherMessage = Base64.getEncoder().encodeToString(cipherMessage);
4. Prepare Decryption: Deserialize
If you have encoded the message, first decode it to byte array:
byte[] cipherMessage = Base64.getDecoder().decode(base64CipherMessage)
Important:
be careful to validate input parameters, so to avoid denial of service attacks by allocating too much memory.
5. Decrypt
Initialize the cipher and set the same parameters as with the encryption:
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
//use first 12 bytes for iv
AlgorithmParameterSpec gcmIv = new GCMParameterSpec(128, cipherMessage, 0, 12);
cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmIv);
//use everything from 12 bytes on as ciphertext
byte[] plainText = cipher.doFinal(cipherMessage, 12, cipherMessage.length - 12);
Important:
don't forget to add associated data with cipher.updateAAD(associatedData); if you added it during encryption.
A working code snippet can be found in this gist.
Note that most recent Android (SDK 21+) and Java (7+) implementations should have AES-GCM. Older versions may lack it. I still choose this mode, since it is easier to implement in addition to being more efficient compared to similar mode of Encrypt-then-Mac (with e.g. AES-CBC + HMAC). See this article on how to implement AES-CBC with HMAC.
Here a solution without Apache Commons Codec's Base64:
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
public class AdvancedEncryptionStandard
{
private byte[] key;
private static final String ALGORITHM = "AES";
public AdvancedEncryptionStandard(byte[] key)
{
this.key = key;
}
/**
* Encrypts the given plain text
*
* #param plainText The plain text to encrypt
*/
public byte[] encrypt(byte[] plainText) throws Exception
{
SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return cipher.doFinal(plainText);
}
/**
* Decrypts the given byte array
*
* #param cipherText The data to decrypt
*/
public byte[] decrypt(byte[] cipherText) throws Exception
{
SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return cipher.doFinal(cipherText);
}
}
Usage example:
byte[] encryptionKey = "MZygpewJsCpRrfOr".getBytes(StandardCharsets.UTF_8);
byte[] plainText = "Hello world!".getBytes(StandardCharsets.UTF_8);
AdvancedEncryptionStandard advancedEncryptionStandard = new AdvancedEncryptionStandard(
encryptionKey);
byte[] cipherText = advancedEncryptionStandard.encrypt(plainText);
byte[] decryptedCipherText = advancedEncryptionStandard.decrypt(cipherText);
System.out.println(new String(plainText));
System.out.println(new String(cipherText));
System.out.println(new String(decryptedCipherText));
Prints:
Hello world!
դ;��LA+�ߙb*
Hello world!
Looks to me like you are not dealing properly with your Initialization Vector (IV).
It's been a long time since I last read about AES, IVs and block chaining, but your line
IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
does not seem to be OK. In the case of AES, you can think of the initialization vector as the "initial state" of a cipher instance, and this state is a bit of information that you can not get from your key but from the actual computation of the encrypting cipher. (One could argue that if the IV could be extracted from the key, then it would be of no use, as the key is already given to the cipher instance during its init phase).
Therefore, you should get the IV as a byte[] from the cipher instance at the end of your encryption
cipherOutputStream.close();
byte[] iv = encryptCipher.getIV();
and you should initialize your Cipher in DECRYPT_MODE with this byte[] :
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Then, your decryption should be OK.
Hope this helps.
The IV that your using for decryption is incorrect. Replace this code
//Decrypt cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);
With this code
//Decrypt cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(encryptCipher.getIV());
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);
And that should solve your problem.
Below includes an example of a simple AES class in Java. I do not recommend using this class in production environments, as it may not account for all of the specific needs of your application.
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class AES
{
public static byte[] encrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
{
return AES.transform(Cipher.ENCRYPT_MODE, keyBytes, ivBytes, messageBytes);
}
public static byte[] decrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
{
return AES.transform(Cipher.DECRYPT_MODE, keyBytes, ivBytes, messageBytes);
}
private static byte[] transform(final int mode, final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
{
final SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
final IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
byte[] transformedBytes = null;
try
{
final Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
cipher.init(mode, keySpec, ivSpec);
transformedBytes = cipher.doFinal(messageBytes);
}
catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e)
{
e.printStackTrace();
}
return transformedBytes;
}
public static void main(final String[] args) throws InvalidKeyException, InvalidAlgorithmParameterException
{
//Retrieved from a protected local file.
//Do not hard-code and do not version control.
final String base64Key = "ABEiM0RVZneImaq7zN3u/w==";
//Retrieved from a protected database.
//Do not hard-code and do not version control.
final String shadowEntry = "AAECAwQFBgcICQoLDA0ODw==:ZtrkahwcMzTu7e/WuJ3AZmF09DE=";
//Extract the iv and the ciphertext from the shadow entry.
final String[] shadowData = shadowEntry.split(":");
final String base64Iv = shadowData[0];
final String base64Ciphertext = shadowData[1];
//Convert to raw bytes.
final byte[] keyBytes = Base64.getDecoder().decode(base64Key);
final byte[] ivBytes = Base64.getDecoder().decode(base64Iv);
final byte[] encryptedBytes = Base64.getDecoder().decode(base64Ciphertext);
//Decrypt data and do something with it.
final byte[] decryptedBytes = AES.decrypt(keyBytes, ivBytes, encryptedBytes);
//Use non-blocking SecureRandom implementation for the new IV.
final SecureRandom secureRandom = new SecureRandom();
//Generate a new IV.
secureRandom.nextBytes(ivBytes);
//At this point instead of printing to the screen,
//one should replace the old shadow entry with the new one.
System.out.println("Old Shadow Entry = " + shadowEntry);
System.out.println("Decrytped Shadow Data = " + new String(decryptedBytes, StandardCharsets.UTF_8));
System.out.println("New Shadow Entry = " + Base64.getEncoder().encodeToString(ivBytes) + ":" + Base64.getEncoder().encodeToString(AES.encrypt(keyBytes, ivBytes, decryptedBytes)));
}
}
Note that AES has nothing to do with encoding, which is why I chose to handle it separately and without the need of any third party libraries.
Online Editor Runnable version:-
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
//import org.apache.commons.codec.binary.Base64;
import java.util.Base64;
public class Encryptor {
public static String encrypt(String key, String initVector, String value) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
//System.out.println("encrypted string: "
// + Base64.encodeBase64String(encrypted));
//return Base64.encodeBase64String(encrypted);
String s = new String(Base64.getEncoder().encode(encrypted));
return s;
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static String decrypt(String key, String initVector, String encrypted) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] original = cipher.doFinal(Base64.getDecoder().decode(encrypted));
return new String(original);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static void main(String[] args) {
String key = "Bar12345Bar12345"; // 128 bit key
String initVector = "RandomInitVector"; // 16 bytes IV
System.out.println(encrypt(key, initVector, "Hello World"));
System.out.println(decrypt(key, initVector, encrypt(key, initVector, "Hello World")));
}
}
This is an improvement over the accepted answer.
Changes:
(1) Using random IV and prepend it to the encrypted text
(2) Using SHA-256 to generate a key from a passphrase
(3) No dependency on Apache Commons
public static void main(String[] args) throws GeneralSecurityException {
String plaintext = "Hello world";
String passphrase = "My passphrase";
String encrypted = encrypt(passphrase, plaintext);
String decrypted = decrypt(passphrase, encrypted);
System.out.println(encrypted);
System.out.println(decrypted);
}
private static SecretKeySpec getKeySpec(String passphrase) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
return new SecretKeySpec(digest.digest(passphrase.getBytes(UTF_8)), "AES");
}
private static Cipher getCipher() throws NoSuchPaddingException, NoSuchAlgorithmException {
return Cipher.getInstance("AES/CBC/PKCS5PADDING");
}
public static String encrypt(String passphrase, String value) throws GeneralSecurityException {
byte[] initVector = new byte[16];
SecureRandom.getInstanceStrong().nextBytes(initVector);
Cipher cipher = getCipher();
cipher.init(Cipher.ENCRYPT_MODE, getKeySpec(passphrase), new IvParameterSpec(initVector));
byte[] encrypted = cipher.doFinal(value.getBytes());
return DatatypeConverter.printBase64Binary(initVector) +
DatatypeConverter.printBase64Binary(encrypted);
}
public static String decrypt(String passphrase, String encrypted) throws GeneralSecurityException {
byte[] initVector = DatatypeConverter.parseBase64Binary(encrypted.substring(0, 24));
Cipher cipher = getCipher();
cipher.init(Cipher.DECRYPT_MODE, getKeySpec(passphrase), new IvParameterSpec(initVector));
byte[] original = cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted.substring(24)));
return new String(original);
}
It's often the good idea to rely on standard library provided solution:
private static void stackOverflow15554296()
throws
NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, IllegalBlockSizeException,
BadPaddingException
{
// prepare key
KeyGenerator keygen = KeyGenerator.getInstance("AES");
SecretKey aesKey = keygen.generateKey();
String aesKeyForFutureUse = Base64.getEncoder().encodeToString(
aesKey.getEncoded()
);
// cipher engine
Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
// cipher input
aesCipher.init(Cipher.ENCRYPT_MODE, aesKey);
byte[] clearTextBuff = "Text to encode".getBytes();
byte[] cipherTextBuff = aesCipher.doFinal(clearTextBuff);
// recreate key
byte[] aesKeyBuff = Base64.getDecoder().decode(aesKeyForFutureUse);
SecretKey aesDecryptKey = new SecretKeySpec(aesKeyBuff, "AES");
// decipher input
aesCipher.init(Cipher.DECRYPT_MODE, aesDecryptKey);
byte[] decipheredBuff = aesCipher.doFinal(cipherTextBuff);
System.out.println(new String(decipheredBuff));
}
This prints "Text to encode".
Solution is based on Java Cryptography Architecture Reference Guide and https://stackoverflow.com/a/20591539/146745 answer.
Another solution using java.util.Base64 with Spring Boot
Encryptor Class
package com.jmendoza.springboot.crypto.cipher;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
#Component
public class Encryptor {
#Value("${security.encryptor.key}")
private byte[] key;
#Value("${security.encryptor.algorithm}")
private String algorithm;
public String encrypt(String plainText) throws Exception {
SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return new String(Base64.getEncoder().encode(cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8))));
}
public String decrypt(String cipherText) throws Exception {
SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return new String(cipher.doFinal(Base64.getDecoder().decode(cipherText)));
}
}
EncryptorController Class
package com.jmendoza.springboot.crypto.controller;
import com.jmendoza.springboot.crypto.cipher.Encryptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/cipher")
public class EncryptorController {
#Autowired
Encryptor encryptor;
#GetMapping(value = "encrypt/{value}")
public String encrypt(#PathVariable("value") final String value) throws Exception {
return encryptor.encrypt(value);
}
#GetMapping(value = "decrypt/{value}")
public String decrypt(#PathVariable("value") final String value) throws Exception {
return encryptor.decrypt(value);
}
}
application.properties
server.port=8082
security.encryptor.algorithm=AES
security.encryptor.key=M8jFt46dfJMaiJA0
Example
http://localhost:8082/cipher/encrypt/jmendoza
2h41HH8Shzc4BRU3hVDOXA==
http://localhost:8082/cipher/decrypt/2h41HH8Shzc4BRU3hVDOXA==
jmendoza
Optimized version of the accepted answer.
no 3rd party libs
includes IV into the encrypted message (can be public)
password can be of any length
Code:
import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class Encryptor {
public static byte[] getRandomInitialVector() {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
SecureRandom randomSecureRandom = SecureRandom.getInstance("SHA1PRNG");
byte[] initVector = new byte[cipher.getBlockSize()];
randomSecureRandom.nextBytes(initVector);
return initVector;
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static byte[] passwordTo16BitKey(String password) {
try {
byte[] srcBytes = password.getBytes("UTF-8");
byte[] dstBytes = new byte[16];
if (srcBytes.length == 16) {
return srcBytes;
}
if (srcBytes.length < 16) {
for (int i = 0; i < dstBytes.length; i++) {
dstBytes[i] = (byte) ((srcBytes[i % srcBytes.length]) * (srcBytes[(i + 1) % srcBytes.length]));
}
} else if (srcBytes.length > 16) {
for (int i = 0; i < srcBytes.length; i++) {
dstBytes[i % dstBytes.length] += srcBytes[i];
}
}
return dstBytes;
} catch (UnsupportedEncodingException ex) {
ex.printStackTrace();
}
return null;
}
public static String encrypt(String key, String value) {
return encrypt(passwordTo16BitKey(key), value);
}
public static String encrypt(byte[] key, String value) {
try {
byte[] initVector = Encryptor.getRandomInitialVector();
IvParameterSpec iv = new IvParameterSpec(initVector);
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
return Base64.getEncoder().encodeToString(encrypted) + " " + Base64.getEncoder().encodeToString(initVector);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static String decrypt(String key, String encrypted) {
return decrypt(passwordTo16BitKey(key), encrypted);
}
public static String decrypt(byte[] key, String encrypted) {
try {
String[] encryptedParts = encrypted.split(" ");
byte[] initVector = Base64.getDecoder().decode(encryptedParts[1]);
if (initVector.length != 16) {
return null;
}
IvParameterSpec iv = new IvParameterSpec(initVector);
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] original = cipher.doFinal(Base64.getDecoder().decode(encryptedParts[0]));
return new String(original);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
}
Usage:
String key = "Password of any length.";
String encrypted = Encryptor.encrypt(key, "Hello World");
String decrypted = Encryptor.decrypt(key, encrypted);
System.out.println(encrypted);
System.out.println(decrypted);
Example output:
QngBg+Qc5+F8HQsksgfyXg== yDfYiIHTqOOjc0HRNdr1Ng==
Hello World
I am encrypting a string using AES and want to decrypt it on other pc. The encryption and decryption works if i execute on the same pc. But for decryption on other pc my encryption algo is generating a "Cipher cipher" which is needed for decryption on other side along with the key. I am not sure how do I transfer the cipher to the other side.
here is my code
public class AESCrypt {
static String plainText;
static byte[] plainBytesDecrypted = new byte[1024];
static byte[] cipherBytes = new byte[1024];
static SecretKey key;
static Cipher cipher;
public AESCrypt() throws NoSuchAlgorithmException {
KeyGenerator generator = KeyGenerator.getInstance("AES");
generator.init(128);
key = generator.generateKey();
}
public static byte[] encryption(String plainBytes) {
try {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "BC");
cipher.init(Cipher.ENCRYPT_MODE, key);
cipherBytes = cipher.doFinal(plainBytes.getBytes());
System.out.println("Encrypted data : " + new String(cipherBytes));
} catch (Exception ex) {
ex.printStackTrace();
}
return cipherBytes;
}
private static String bytesToHex(byte[] hash) {
return DatatypeConverter.printHexBinary(hash);
}
public static String Decryption() throws InvalidKeyException, IllegalBlockSizeException, InvalidAlgorithmParameterException, BadPaddingException {
cipher.init(Cipher.DECRYPT_MODE, key, cipher.getParameters());
plainBytesDecrypted = cipher.doFinal(cipherBytes);
System.out.println("Decrypted data : " + new String(plainBytesDecrypted));
return (new String(plainBytesDecrypted));
}
}
You have 3 choices in general:
Hardcode the cipher/mode/padding, so that both sides are aware you're always using the same format.
Send the cipher/mode/padding before the secret messages, so the other side knows what cipher to initialise.
Use an existing protocol, which already includes all those things for you and does it securely. You can send your data inside of TLS connection, for example.
I'm searching a known encryption algorithm that would let me encrypt data with a key and if the data is decrypted with another key would give me data that is different from the first one. It's string data. I tried AES but the problem is that the result of decryption using a wrong key gives a lot of invalid symbols so the resulting String is differentiable from a result given with a good key.
Is there a work around to this?
I did this so far:
private static final String truc = "f41a3ff27aab7d5c";
public static String encryptPass(String pass,String key) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException, InvalidAlgorithmParameterException {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(key.getBytes("UTF-8"));
byte[] digest = md.digest();
BCrypt bcrypt = new BCrypt();
SecretKey keyL = new SecretKeySpec(digest, "AES");
Cipher AesCipher = Cipher.getInstance("AES/CTR/NoPadding");
AesCipher.init(Cipher.ENCRYPT_MODE, keyL, new IvParameterSpec(truc.getBytes()));
byte[] encVal = AesCipher.doFinal(pass.getBytes());
pass = Base64.encodeToString(encVal, Base64.DEFAULT);
Log.i("ADA", "encoded pass: " + pass);
return pass;
}
public static String decryptPass(String encPass , String key) throws NoSuchAlgorithmException, UnsupportedEncodingException, NoSuchPaddingException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(key.getBytes("UTF-8"));
byte[] digest = md.digest();
SecretKey keyL = new SecretKeySpec(digest, "AES");
Cipher AesCipher = Cipher.getInstance("AES/CTR/NoPadding");
AesCipher.init(Cipher.DECRYPT_MODE, keyL, new IvParameterSpec(truc.getBytes()));
byte[] decodedValue = Base64.decode(encPass, Base64.DEFAULT);
byte[] decValue = AesCipher.doFinal(decodedValue);
String decryptedValue = new String(decValue);
Log.i("ADA", "decrpyted pass: " + decryptedValue);
return decryptedValue;
}
After reading the answer of MaybeWeCouldStartAVar, last paragraph I used "AES/CTR/NoPadding".
When I'm saying a well formed String I'm expecting mostly ASCII chars 32 to 125.
Is there something out there that would respond to my needs, or should I just roll my own encryption algorithm (I heard it's not advised but if it's my only option well..)?
I'm not trying to restrict access to any data. Data is fully visible by anyone who would use the app but unless they have the right key they should see erroneous data (that don't obviously look erroneous).
The idea is to restrain brute force.