I am new java to encryption. Trying to implement something light weight to encrypt a string and store it somewhere and de-crypt it back before using.
With some web search I came up with this for encryption and decryption.
public static String base64Encode(byte[] bytes)
{
return new BASE64Encoder().encode(bytes);
}
public static byte[] base64Decode(String property) throws IOException
{
return new BASE64Decoder().decodeBuffer(property);
}
public static String encrypt(String mystring) throws GeneralSecurityException, UnsupportedEncodingException
{
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = keyFactory.generateSecret(new PBEKeySpec(mystring.toCharArray()));
Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
pbeCipher.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(SALT, 20));
return base64Encode(pbeCipher.doFinal(mystring.getBytes("UTF-8")));
}
public static String decrypt(String estring) throws GeneralSecurityException, IOException
{
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = keyFactory.generateSecret(new PBEKeySpec(estring.toCharArray()));
Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
pbeCipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(SALT, 20));
return new String(pbeCipher.doFinal(base64Decode(estring)), "UTF-8");
}
I see that encryption worked but I saw a padding related exception in the decryption part, from the doFinal block. Here it is...
encrypted string:zdrtgOKfkZMgpCOflr1ILQ== -> Encrypted String
exceptionjavax.crypto.BadPaddingException: Given
final block not properly padded -> Exception from the doFinal block.
Seems like when I encrypted it, I need to do some kind of padding.
Can any one tell me what went wrong and how can it be fixed?
Thanks
Tas
You are using password based encryption here. This means that the encryption key itself is based upon a password. You must use the same password for both encryption and decryption.
private static char[] ENCRYPTION_PASSWORD
= "some password populated by configuration".toCharArray();
public static String encrypt(String mystring)
throws GeneralSecurityException, UnsupportedEncodingException {
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = keyFactory.generateSecret(new PBEKeySpec(ENCRYPTION_PASSWORD));
Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
pbeCipher.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(SALT, 20));
return DatatypeConverter
.printBase64Binary(pbeCipher.doFinal(mystring.getBytes("UTF-8")));
}
public static String decrypt(String string)
throws GeneralSecurityException, IOException {
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = keyFactory.generateSecret(new PBEKeySpec(ENCRYPTION_PASSWORD));
Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
pbeCipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(SALT, 20));
return new String(pbeCipher.doFinal(DatatypeConverter
.parseBase64Binary(estring)), "UTF-8");
}
Note also the use of javax.xml.bind.DatatypeConverter for base64 operations. No need to write your own or use third parties these days.
You will need to first decode your input to the decrypt method.
Call the base64Decode method and the estring parameter in your decrypt method.
That should do it.
Change your code ,
SecretKey key = keyFactory.generateSecret(new PBEKeySpec(mystring.toCharArray()));
to
SecretKey key = keyFactory.generateSecret(new PBEKeySpec("PASSWORD".toCharArray(), SALT, 20));
Related
First, please excuse my bad english, this is not my native language.
i am trying to encode and then decode some text of different lengths.
I tried to encode some text with RSA algorithm in java. In the progress, I found out that RSA-encoding is limited to a specific size of bytes, so this approach doesn't fit my specs as my text could be large.
As I want to stay with a public and a private key only, I came up with the idea to use a 'hard-coded' AES key which will be encoded with RSA public key and than use this to encode the data. (see code)
//harcoded secretkey base64 encoded
private static final String secretkey_base64 = "xtnN6Pove5AovbLXtGRJKw==";
//this is how RSA key are generated
public static KeyPair genKeys() throws Exception {
KeyPairGenerator kpg = KeyPairGenerator.getInstance( "RSA" );
kpg.initialize(2048);
KeyPair kp = kpg.generateKeyPair();
return kp;
}
//here text should be encoded
public static String encodeText(String plainText, PublicKey publicKey) throws Exception
{
Cipher encryptCipher = Cipher.getInstance("RSA");
encryptCipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] cipherText = encryptCipher.doFinal(CryptoUtil.secretkey_base64.getBytes(UTF_8));
String encodedSecKey = Base64.getEncoder().encodeToString(cipherText);
SecretKeySpec k = new SecretKeySpec(Base64.getDecoder().decode(encodedSecKey), "AES");
Cipher aesCipher = Cipher.getInstance("AES");
aesCipher.init(Cipher.ENCRYPT_MODE, k);
byte[] byteCipherText = aesCipher.doFinal(plainText.getBytes());
return Base64.getEncoder().encodeToString(byteCipherText);
}
Obviously this doesn't work because the RSA encoded AES key is too long for AES encryption. Throws "Invalid AES key length: 256 bytes"
Does anyone have an idea how I could get the encryption running with this particular approach ?
Is this even possible, or do I have to use a different approach?
Thanks in advance.
//edit - Solved thanks to #ewramner
// here is the working solution:
public static String encodeText(String plainText, PublicKey publicKey) throws Exception
{
KeyGenerator generator = KeyGenerator.getInstance("AES");
generator.init(128); // The AES key size in number of bits
SecretKey secKey = generator.generateKey();
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.PUBLIC_KEY, publicKey);
byte[] encSecKey = cipher.doFinal(secKey.getEncoded());
Cipher aesCipher = Cipher.getInstance("AES");
aesCipher.init(Cipher.ENCRYPT_MODE, secKey);
byte[] byteCipherText = aesCipher.doFinal(plainText.getBytes());
return Base64.getEncoder().encodeToString(encSecKey) + "#" + Base64.getEncoder().encodeToString(byteCipherText);
}
public static String decodeText(String cipherText, PrivateKey privateKey) throws Exception
{
String[] split = cipherText.split("#");
String encSecKey = split[0];
String encText = split[1];
byte[] bytesSecKey = Base64.getDecoder().decode(encSecKey);
byte[] bytesText = Base64.getDecoder().decode(encText);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.PRIVATE_KEY, privateKey);
byte[] decSecKey = cipher.doFinal(bytesSecKey);
SecretKey secKey = new SecretKeySpec(decSecKey , 0, decSecKey .length, "AES");
Cipher aesCipher = Cipher.getInstance("AES");
aesCipher.init(Cipher.DECRYPT_MODE, secKey);
return new String(aesCipher.doFinal(bytesText));
}
I wrote a simple Encryption and Decryption helper class for my android app to encrypt and store Strings securely.
It consists of a single static public method to encrypt, then it calls a private static method to decrypt the encrypted message and returns it. I wrote the method this way to check if the message is intact after encryption/decryption.
I wrote a simple JUnit test with a String and called AssertEquals on the String before and after sending it to the Crypto encryption method.
I get this following errors from running the test:
javax.crypto.AEADBadTagException: Tag mismatch!
The error stack:
at com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:571)
at com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1046)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:983)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:845)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
at javax.crypto.Cipher.doFinal(Cipher.java:2165)
at util.Crypto.decrypt(Crypto.java:94)
at util.Crypto.encrypt(Crypto.java:64)
at com.example.ali.meappley.CryptoTest.encryptAndDecryptTest(CryptoTest.java:29)
I'm new to cryptography, but I read different stackoverflow replies and couldn't find anything of help. Some users suggested calling cipher.update(someByteArray) before calling cipher.doFinal(someByteArray) but I couldnt manage to get it working. Any suggestions?
This is my helper class
public class Crypto {
//public methods
//public static encrypt method
public static String encrypt(String messageToEncrypt, #Nullable byte[] associatedData) throws NoSuchPaddingException,
NoSuchAlgorithmException,
InvalidAlgorithmParameterException,
InvalidKeyException,
BadPaddingException,
IllegalBlockSizeException {
byte[] plainBytes = messageToEncrypt.getBytes();
/////////////////////////////////////////////////////////////////
SecureRandom secureRandom = new SecureRandom();
byte[] key = new byte[16];
secureRandom.nextBytes(key);
SecretKey secretKey = new SecretKeySpec(key, "AES");
byte[] iv = new byte[12]; //NEVER REUSE THIS IV WITH SAME KEY
secureRandom.nextBytes(iv);
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);
if (associatedData != null) {
cipher.updateAAD(associatedData);
}
byte[] cipherText = cipher.doFinal(plainBytes);
ByteBuffer byteBuffer = ByteBuffer.allocate(4 + iv.length + cipherText.length);
byteBuffer.putInt(iv.length);
byteBuffer.put(iv);
byteBuffer.put(cipherText);
byte[] cipherMessage = byteBuffer.array();
Arrays.fill(key,(byte) 0); //overwrite the content of key with zeros
///////////////////////////////////////////////////////////////////
byte[] decrypted = decrypt(cipherMessage, null, key);
return decrypted.toString();
}
//public static decrypt method
private static byte[] decrypt(byte[] cipherMessage, #Nullable byte[] associatedData, byte[] key) throws NoSuchPaddingException,
NoSuchAlgorithmException,
InvalidAlgorithmParameterException,
InvalidKeyException,
BadPaddingException,
IllegalBlockSizeException {
ByteBuffer byteBuffer = ByteBuffer.wrap(cipherMessage);
int ivLength = byteBuffer.getInt();
if(ivLength < 12 || ivLength >= 16) { // check input parameter
throw new IllegalArgumentException("invalid iv length");
}
byte[] iv = new byte[ivLength];
byteBuffer.get(iv);
byte[] cipherText = new byte[byteBuffer.remaining()];
byteBuffer.get(cipherText);
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(128, iv));
if (associatedData != null) {
cipher.updateAAD(associatedData);
}
cipher.update(cipherText);
byte[] plainText= cipher.doFinal(cipherText);
return plainText;
}
There are a few issues with your code:
1) In your encrypt-method remove the following line (or shift it behind the decrypt-call).
Arrays.fill(key, (byte) 0); // overwrite the content of key with zeros
Otherwise the key for encryption and decryption differ.
2) In your encrypt-method also pass the associatedData in your decrypt-call i.e. replace
byte[] decrypted = decrypt(cipherMessage, null, key);
with
byte[] decrypted = decrypt(cipherMessage, associatedData, key);
The associatedData passed for encryption and decryption have to match for validity. For the purpose of the associatedData see e.g. https://crypto.stackexchange.com/questions/6711/how-to-use-gcm-mode-and-associated-data-properly
3) In your decrypt-method remove the line
cipher.update(cipherText);
For the purpose of the update-method see e.g. What does cipher.update do in java?
All three issues give rise to an AEADBadTagException.
4) I suspect for testing purposes your encrypt-method returns decrypted.toString() which however only gives you the object's class and hashcode. It would make more sense to return e.g. new String(decrypted).
the current decryption algorithm I wrote goes as follows,
public String decrypt(String enc) throws Exception
{
Key key = k;
Cipher crypt = Cipher.getInstance("AES");
crypt.init(Cipher.DECRYPT_MODE,key);
byte[] decrypt = crypt.doFinal(enc.getBytes());
return new String(decrypt);
}
The error that I get is at the line
byte[] decrypt = crypt.doFinal(enc.getBytes());
I have seen similar questions as this posted, but I am using a 128 bit key, so I am pretty certain there is no padding.
This is how I generate the key
public static SecretKey getKey() throws Exception
{
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128);
return keyGen.generateKey();
}
Additionally, decoding using base64 gives the same exact error
public String decrypt(String enc) throws Exception
{
Key key = k;
Cipher crypt = Cipher.getInstance("AES");
crypt.init(Cipher.DECRYPT_MODE,key);
byte[] decrypt = crypt.doFinal(Base64.getMimeDecoder().decode(enc));
return new String(decrypt);
}
public String decrypt(String enc)
The problem has already happened by the time you get here. The problem is that you are passing around ciphertext in a String. String isn't a container for binary data. Use a byte[].
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;
}
I have the following test code to encrypt and decrypt a string. It works fine if I leave out the wrapping and unwrapping code for my key in test(), but when I try to wrap my key, and then unwrap it again and use it for the decryption, it fails and I don't get "Test" back as the resulting decrypted string but "�J��" instead.
Does anybody see the error I'm doing with the wrapping and unwrapping? Thanks.
private static void test() throws Exception {
// create wrap key
KeyGenerator keyGenerator = KeyGenerator.getInstance("AESWrap");
keyGenerator.init(256);
Key wrapKey = keyGenerator.generateKey();
SecretKey key = generateKey(PASSPHRASE);
Cipher cipher;
// wrap key
cipher = Cipher.getInstance("AESWrap");
cipher.init(Cipher.WRAP_MODE, wrapKey);
byte[] wrappedKeyBytes = cipher.wrap(key);
// unwrap key again
cipher.init(Cipher.UNWRAP_MODE, wrapKey);
key = (SecretKey)cipher.unwrap( wrappedKeyBytes, "AES/CTR/NOPADDING", Cipher.SECRET_KEY);
// encrypt
cipher = Cipher.getInstance("AES/CTR/NOPADDING");
cipher.init(Cipher.ENCRYPT_MODE, key, generateIV(cipher), random);
byte[] b = cipher.doFinal("Test".toString().getBytes());
// decrypt
cipher = Cipher.getInstance("AES/CTR/NOPADDING");
cipher.init(Cipher.DECRYPT_MODE, key, generateIV(cipher), random);
b = cipher.doFinal(b);
System.out.println(new String(b));
// should output "Test", but outputs �J�� if wrapping/unwrapping
}
And the two helper methods that are called in the code above:
private static IvParameterSpec generateIV(Cipher cipher) throws Exception {
byte [] ivBytes = new byte[cipher.getBlockSize()];
random.nextBytes(ivBytes); // random = new SecureRandom();
return new IvParameterSpec(ivBytes);
}
private static SecretKey generateKey(String passphrase) throws Exception {
PBEKeySpec keySpec = new PBEKeySpec(passphrase.toCharArray(), salt.getBytes(), iterations, keyLength);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(PBE_ALGORITHM); //"PBEWITHSHA256AND256BITAES-CBC-BC"
return keyFactory.generateSecret(keySpec);
}
It looks like you're giving a different IV to cipher.init(Cipher.ENCRYPT_MODE, ...) and cipher.init(Cipher.DECRYPT_MODE, ...) by calling generateIV() twice.