I am trying to access my encrypt key stored in application.properties and set it as SECRET property in my AttributeEncryptor.
This is the class:
package com.nimesia.sweetvillas.encryptors;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.spec.SecretKeySpec;
import javax.persistence.AttributeConverter;
import java.security.InvalidKeyException;
import java.security.Key;
import java.util.Base64;
#Component
public class AttributeEncryptor implements AttributeConverter<String, String> {
private static final String AES = "AES";
#Value("${datasource.encryptkey}")
private String SECRET;
private final Key key;
private final Cipher cipher;
public AttributeEncryptor() throws Exception {
key = new SecretKeySpec(SECRET.getBytes(), AES);
cipher = Cipher.getInstance(AES);
}
#Override
public String convertToDatabaseColumn(String attribute) {
try {
cipher.init(Cipher.ENCRYPT_MODE, key);
return Base64.getEncoder().encodeToString(cipher.doFinal(attribute.getBytes()));
} catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException e) {
throw new IllegalStateException(e);
}
}
#Override
public String convertToEntityAttribute(String dbData) {
try {
cipher.init(Cipher.DECRYPT_MODE, key);
return new String(cipher.doFinal(Base64.getDecoder().decode(dbData)));
} catch (InvalidKeyException | BadPaddingException | IllegalBlockSizeException e) {
throw new IllegalStateException(e);
}
}
}
datasource.encryptkey is in application.properties.
I have tried to access it from a controller and it worked. But when I try and use it in here it gives me a NullPointerException.
Hope I was clear.
Thanks in advance
The class is created before the property is set!
You should add it to the constructor instead:
private final String SECRET;
private final Key key;
private final Cipher cipher;
public AttributeEncryptor(#Value("${datasource.encryptkey}") String secret) throws Exception {
SECRET = secret;
key = new SecretKeySpec(SECRET.getBytes(), AES);
cipher = Cipher.getInstance(AES);
}
I am trying to understand how I can decrypt a String in one application that I have encrypted in a separate application. I can do this when I am in a single application execution, and I use the PrivateKey from the same KeyPair that generated the PublicKey. However, I want to encrypt the value in one application, and decrypt the value in another application. Basically I have a web service sending value I do not want to be tampered with to an application, and I was planning to use asymmetric encryption to handle it. Feel free to let me know if I am going about solving that the wrong way.
I have tried various encryption techniques, starting with Symmetric. The issue I ran into with that is that the value is encrypted to the same text each time - not very useful if my goal is to keep someone from tampering with a value - once they know the encrypted version of some text, they will be able to use that in any request. I have been trying to follow the standards here - https://www.veracode.com/blog/research/encryption-and-decryption-java-cryptography
I can get all my examples working in a single transaction doing the encrypt/decrypt - it is just when I try to encrypt in one request, and decrypt in a second request that I fail.
Here is my current working example:
package com.ni.apps.cartencryptutil;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.xml.bind.DatatypeConverter;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
/**
* Security class for encrypting and decrypting Strings (that also works in WC ver 7 and above)
*
* #author
*
*/
public class SecurityTools {
private static final Logger logger = Logger.getLogger(SecurityTools.class);
private Cipher cipher;
private IvParameterSpec initVector;
private SecretKey secretKey;
PrivateKey privateKeyParam;
PublicKey publicKeyParam;
private static SecureRandom secureRandom = new SecureRandom();
private static final String TRANSFORMATION = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
private static final String ALGORITHM_SHORT = "RSA";
private static final String RNG_ALGORITHM = "SHA1PRNG";
private static final String ALGORITHM = "PBKDF2WithHmacSHA256";
private static final String UTF8 = "UTF-8";
// exceptions
private static final String _ERR_ILLEGAL_BLOCK_SIZE = "illegal block size exception.";
private static final String _ERR_BAD_PADDING = "bad padding exception.";
private static final String _ERR_INVALIDKEY = "invalidkey exception.";
private static final String _ERR_PADDING = "padding exception.";
private static final String _ERR_NO_SUCH_ALGORITHM = "no such algorithm exception.";
private static final String _ERR_PASSPHRASE_IS_NULL = "passphrase is null.";
private static final String _ERR_INVALID_ALGORITHM = "invalid algorithm exception.";
private static final String _ERR_UNSUPPORTED_ENCODING = "encoding not supported.";
private static final String _ERR_INVALID_KEY_SPEC = "invalid key spec exception.";
/**
* Constructor
*
* #throws EncryptionException
*/
public SecurityTools() throws EncryptionException {
if (logger.isDebugEnabled()) {
logger.debug("entering Constructor");
}
try {
cipher = Cipher.getInstance(ALGORITHM_SHORT);
generateIV();
generateKeys();
} catch (NoSuchAlgorithmException iae) {
logger.error(_ERR_NO_SUCH_ALGORITHM, iae);
throw new EncryptionException(_ERR_NO_SUCH_ALGORITHM, iae);
} catch (NoSuchPaddingException nspe) {
logger.error(_ERR_PADDING, nspe);
throw new EncryptionException(_ERR_PADDING, nspe);
}
if (logger.isDebugEnabled()) {
logger.debug("exiting Constructor");
}
}
/**
* Encrypts a given plain text String, and returns the encrypted String
*
* #param plainText
* #return
* #throws EncryptionException
*/
public String encrypt(String plainText, PublicKey publicKey) throws EncryptionException {
if (logger.isDebugEnabled()) {
logger.debug("entering encrypt");
}
String encryptedKey = null;
try {
byte[] byteToEncrypt = plainText.getBytes(UTF8);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedBytes = cipher.doFinal(byteToEncrypt);
encryptedKey = DatatypeConverter.printBase64Binary(encryptedBytes);
} catch (IllegalArgumentException iae) {
logger.error(_ERR_PASSPHRASE_IS_NULL, iae);
throw new EncryptionException(_ERR_PASSPHRASE_IS_NULL, iae);
} catch (InvalidKeyException ike) {
logger.error(_ERR_INVALIDKEY, ike);
throw new EncryptionException(_ERR_INVALIDKEY, ike);
} catch (BadPaddingException bpe) {
logger.error(_ERR_BAD_PADDING, bpe);
throw new EncryptionException(_ERR_BAD_PADDING, bpe);
} catch (IllegalBlockSizeException bpe) {
logger.error(_ERR_ILLEGAL_BLOCK_SIZE, bpe);
throw new EncryptionException(_ERR_ILLEGAL_BLOCK_SIZE, bpe);
} catch (UnsupportedEncodingException uee) {
logger.error(_ERR_UNSUPPORTED_ENCODING, uee);
throw new EncryptionException(_ERR_UNSUPPORTED_ENCODING, uee);
} /*-catch (InvalidAlgorithmParameterException iape) {
logger.error(_ERR_INVALID_ALGORITHM, iape);
throw new EncryptionException(_ERR_INVALID_ALGORITHM, iape);
}*/
if (logger.isDebugEnabled()) {
logger.debug("exiting encrypt");
}
return encryptedKey;
}
/**
* Decrypts a given encrypted String, and returns the plain text String
*
* #param cipherTextStr
* #return
* #throws EncryptionException
*/
public String decrypt(String cipherTextStr, PrivateKey privateKey) throws EncryptionException {
if (logger.isDebugEnabled()) {
logger.debug("entering decrypt");
}
String cleartext = null;
try {
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] plainByte = cipher.doFinal(DatatypeConverter.parseBase64Binary(cipherTextStr));
cleartext = new String(plainByte);
} /*-catch (InvalidAlgorithmParameterException iape) {
logger.error(_ERR_INVALID_ALGORITHM, iape);
throw new EncryptionException(_ERR_INVALID_ALGORITHM, iape);
} */catch (IllegalArgumentException iae) {
logger.error(_ERR_PASSPHRASE_IS_NULL, iae);
throw new EncryptionException(_ERR_PASSPHRASE_IS_NULL, iae);
} catch (InvalidKeyException ike) {
logger.error(_ERR_INVALIDKEY, ike);
throw new EncryptionException(_ERR_INVALIDKEY, ike);
} catch (BadPaddingException bpe) {
logger.error(_ERR_BAD_PADDING, bpe);
throw new EncryptionException(_ERR_BAD_PADDING, bpe);
} catch (IllegalBlockSizeException bpe) {
logger.error(_ERR_ILLEGAL_BLOCK_SIZE, bpe);
throw new EncryptionException(_ERR_ILLEGAL_BLOCK_SIZE, bpe);
}
if (logger.isDebugEnabled()) {
logger.debug("exiting decrypt");
}
return cleartext;
}
/**
* Creates the IV using Secure Random Number Generator and an empty 16byte array
*
* #return
*/
private void generateIV() {
if (logger.isDebugEnabled()) {
logger.debug("entering generateIV");
}
byte[] newSeed = secureRandom.generateSeed(16);
secureRandom.setSeed(newSeed);
byte[] byteIV = new byte[16];
secureRandom.nextBytes(byteIV);
initVector = new IvParameterSpec(byteIV);
if (logger.isDebugEnabled()) {
logger.debug("exiting generateIV");
}
}
/**
* Generates the Key used for decryption and encryption
*
* #throws EncryptionException
*/
private void generateKeys() throws EncryptionException {
try {
String saltStr = "salty";// rbConfig.getString("salt");
String passPhraseStr = "passy";// rbConfig.getString("passphrase");
if (StringUtils.isEmpty(saltStr) || StringUtils.isEmpty(passPhraseStr)) {
throw new EncryptionException(_ERR_PASSPHRASE_IS_NULL);
}
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(512); // key size specified here.
KeyPair pair = keyGen.generateKeyPair();
privateKeyParam = pair.getPrivate();
publicKeyParam = pair.getPublic();
/*-byte[] salt = saltStr.getBytes();
int iterations = 10000;
int keyLength = 128;
SecretKeyFactory factory = SecretKeyFactory.getInstance(ALGORITHM);
SecretKey tmp = factory.generateSecret(new PBEKeySpec(passPhraseStr.toCharArray(), salt, iterations, keyLength));
secretKey = new SecretKeySpec(tmp.getEncoded(), ALGORITHM_SHORT);*/
} catch (NoSuchAlgorithmException iae) {
logger.error(_ERR_NO_SUCH_ALGORITHM, iae);
throw new EncryptionException(_ERR_NO_SUCH_ALGORITHM, iae);
} /*-catch (InvalidKeySpecException e) {
logger.error(_ERR_INVALID_KEY_SPEC, e);
throw new EncryptionException(_ERR_INVALID_KEY_SPEC, e);
}*/
}
/**
* Test method
*
* #param args
*/
public static void main(String[] args) {
String[] message = { "mktest", "9248547896548752345", "okok234234234okok467467",
"12" };
String result = null;
try {
SecurityTools secTool = new SecurityTools();
PrivateKey priv = secTool.getPrivateKeyParam();
PublicKey publ = secTool.getPublicKeyParam();
String temp = "N5B1zgbvts3Vwrt6qyL/TBzt62HTFz0ISySx5HFu02oVq1YEhFLbrgdCndROX4/5hMpxCHGM8UJBSyZUfjD/DA==";
// System.out.println("ASYMMETRIC TEST" + secTool.decrypt(temp, priv));
for (String mess : message) {
result = secTool.encrypt(mess, publ);
System.out.println(result);
result = secTool.decrypt(result, priv);
System.out.println(result);
}
} catch (Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}
/**
* #return the privateKeyParam
*/
public PrivateKey getPrivateKeyParam() {
return privateKeyParam;
}
/**
* #return the publicKeyParam
*/
public PublicKey getPublicKeyParam() {
return publicKeyParam;
}
class EncryptionException extends Exception {
private static final long serialVersionUID = 1L;
public EncryptionException() {}
public EncryptionException(String message, Throwable cause) {
super(message, cause);
}
public EncryptionException(String message) {
super(message);
}
public EncryptionException(Throwable cause) {
super(cause);
}
}
}
When I run that as-is it works because the main is doing the encryption and decryption all in one go. However, when I take the output from a run, and store it in the temp String in main(), and then uncomment the line System.out.println("ASYMMETRIC TEST" + is when it fails with
com.ni.apps.cartencryptutil.SecurityTools$EncryptionException: bad padding exception.
at com.ni.apps.cartencryptutil.SecurityTools.decrypt(SecurityTools.java:154)
at com.ni.apps.cartencryptutil.SecurityTools.main(SecurityTools.java:239)
Caused by: javax.crypto.BadPaddingException: Decryption error
at sun.security.rsa.RSAPadding.unpadV15(RSAPadding.java:380)
at sun.security.rsa.RSAPadding.unpad(RSAPadding.java:291)
at com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:363)
at com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:389)
at javax.crypto.Cipher.doFinal(Cipher.java:2165)
at com.ni.apps.cartencryptutil.SecurityTools.decrypt(SecurityTools.java:141)
... 1 more
I assume my issue is that I am re-generating a new KeyPair each time, which will only work the one time as a pair. So once I try to match a PrivateKey that came from one KeyPair to the text that was encrypted with an entirely different KeyPair, it fails. But I have yet to find how you are supposed to otherwise achieve this in that case.
package com.ni.apps.cartencryptutil;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import com.ni.apps.cartencryptutil.exceptions.EncryptionException;
/**
* Security class for encrypting and decrypting Strings (that also works in WC ver 7 and above).
*
* #author mkohanek
*
*/
public class SecurityTools {
private static final Logger logger = Logger.getLogger(SecurityTools.class);
private Cipher cipher;
private IvParameterSpec initVector;
private SecretKey secretKey;
private transient ResourceBundle rbConfig;
private static SecureRandom secureRandom = new SecureRandom();
private static final String AES_ALGORITHM = "AES/CBC/PKCS5Padding";
private static final String ALGORITHM_SHORT = "AES";
private static final String PBE_ALGORITHM = "PBEWithMD5AndDES";
private static final String UTF8 = "UTF-8";
private static final int ITERATIONS = 10000;
private static final int KEY_LENGTH = 128;
// exceptions
private static final String _ERR_ILLEGAL_BLOCK_SIZE = "illegal block size exception.";
private static final String _ERR_BAD_PADDING = "bad padding exception.";
private static final String _ERR_INVALIDKEY = "invalidkey exception.";
private static final String _ERR_PADDING = "padding exception.";
private static final String _ERR_NO_SUCH_ALGORITHM = "no such algorithm exception.";
private static final String _ERR_PASSPHRASE_IS_NULL = "passphrase is null.";
private static final String _ERR_SALT_IS_NULL = "salt is null.";
private static final String _ERR_INVALID_ALGORITHM = "invalid algorithm exception.";
private static final String _ERR_UNSUPPORTED_ENCODING = "encoding not supported.";
private static final String _ERR_INVALID_KEY_SPEC = "invalid key spec exception.";
/**
* Constructor
*
* #param salt
* - for encryption, obtain salt from generateSalt(). for decryption, you should use the same salt used during encryption
* #throws EncryptionException
*/
public SecurityTools(String salt) throws EncryptionException {
if (logger.isDebugEnabled()) {
logger.debug("entering Constructor");
}
try {
loadProperties();
cipher = Cipher.getInstance(AES_ALGORITHM);
generateIV(salt);
generateKey(salt);
} catch (NoSuchAlgorithmException iae) {
logger.error(_ERR_NO_SUCH_ALGORITHM, iae);
throw new EncryptionException(_ERR_NO_SUCH_ALGORITHM, iae);
} catch (NoSuchPaddingException nspe) {
logger.error(_ERR_PADDING, nspe);
throw new EncryptionException(_ERR_PADDING, nspe);
}
if (logger.isDebugEnabled()) {
logger.debug("exiting Constructor");
}
}
/**
* Constructor
*
* #param salt
* - for encryption, obtain salt from generateSalt(). for decryption, you should use the same salt used during encryption
* #param passPhrase
* - Allows client to pass in a passphrase it wants to use rather than use the one this library defines. This must also be used when decrypting. The length limit for this key is 16
* characters
* #throws EncryptionException
*/
public SecurityTools(String salt, String passPhrase) throws EncryptionException {
if (logger.isDebugEnabled()) {
logger.debug("entering Constructor");
}
try {
loadProperties();
cipher = Cipher.getInstance(AES_ALGORITHM);
generateIV(salt);
generateKey(salt, passPhrase);
} catch (NoSuchAlgorithmException iae) {
logger.error(_ERR_NO_SUCH_ALGORITHM, iae);
throw new EncryptionException(_ERR_NO_SUCH_ALGORITHM, iae);
} catch (NoSuchPaddingException nspe) {
logger.error(_ERR_PADDING, nspe);
throw new EncryptionException(_ERR_PADDING, nspe);
}
if (logger.isDebugEnabled()) {
logger.debug("exiting Constructor");
}
}
/**
* Encrypts a given plain text String, and returns the encrypted String
*
* #param plainText
* #return
* #throws EncryptionException
*/
public String encrypt(String plainText) throws EncryptionException {
if (logger.isDebugEnabled()) {
logger.debug("entering encrypt");
}
String encryptedKey = null;
try {
byte[] byteToEncrypt = plainText.getBytes(UTF8);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, initVector, new SecureRandom());
byte[] encryptedBytes = cipher.doFinal(byteToEncrypt);
encryptedKey = DatatypeConverter.printBase64Binary(encryptedBytes);
} catch (IllegalArgumentException iae) {
logger.error(_ERR_PASSPHRASE_IS_NULL, iae);
throw new EncryptionException(_ERR_PASSPHRASE_IS_NULL, iae);
} catch (InvalidKeyException ike) {
logger.error(_ERR_INVALIDKEY, ike);
throw new EncryptionException(_ERR_INVALIDKEY, ike);
} catch (BadPaddingException bpe) {
logger.error(_ERR_BAD_PADDING, bpe);
throw new EncryptionException(_ERR_BAD_PADDING, bpe);
} catch (IllegalBlockSizeException bpe) {
logger.error(_ERR_ILLEGAL_BLOCK_SIZE, bpe);
throw new EncryptionException(_ERR_ILLEGAL_BLOCK_SIZE, bpe);
} catch (UnsupportedEncodingException uee) {
logger.error(_ERR_UNSUPPORTED_ENCODING, uee);
throw new EncryptionException(_ERR_UNSUPPORTED_ENCODING, uee);
} catch (InvalidAlgorithmParameterException iape) {
logger.error(_ERR_INVALID_ALGORITHM, iape);
throw new EncryptionException(_ERR_INVALID_ALGORITHM, iape);
}
if (logger.isDebugEnabled()) {
logger.debug("exiting encrypt");
}
return encryptedKey;
}
/**
* Decrypts a given encrypted String, and returns the plain text String
*
* #param cipherTextStr
* #return
* #throws EncryptionException
*/
public String decrypt(String cipherTextStr) throws EncryptionException {
if (logger.isDebugEnabled()) {
logger.debug("entering decrypt");
}
String cleartext = null;
try {
cipher.init(Cipher.DECRYPT_MODE, secretKey, initVector);
byte[] plainByte = cipher.doFinal(DatatypeConverter.parseBase64Binary(cipherTextStr));
cleartext = new String(plainByte);
} catch (InvalidAlgorithmParameterException iape) {
logger.error(_ERR_INVALID_ALGORITHM, iape);
throw new EncryptionException(_ERR_INVALID_ALGORITHM, iape);
} catch (IllegalArgumentException iae) {
logger.error(_ERR_PASSPHRASE_IS_NULL, iae);
throw new EncryptionException(_ERR_PASSPHRASE_IS_NULL, iae);
} catch (InvalidKeyException ike) {
logger.error(_ERR_INVALIDKEY, ike);
throw new EncryptionException(_ERR_INVALIDKEY, ike);
} catch (BadPaddingException bpe) {
logger.error(_ERR_BAD_PADDING, bpe);
throw new EncryptionException(_ERR_BAD_PADDING, bpe);
} catch (IllegalBlockSizeException bpe) {
logger.error(_ERR_ILLEGAL_BLOCK_SIZE, bpe);
throw new EncryptionException(_ERR_ILLEGAL_BLOCK_SIZE, bpe);
}
if (logger.isDebugEnabled()) {
logger.debug("exiting decrypt");
}
return cleartext;
}
/**
* Creates the IV using Secure Random Number Generator and an empty 16byte array
*
* #return
*/
private void generateIV(String salt) {
if (logger.isDebugEnabled()) {
logger.debug("entering generateIV");
}
byte[] newSeed = DatatypeConverter.parseBase64Binary(salt);
initVector = new IvParameterSpec(newSeed);
if (logger.isDebugEnabled()) {
logger.debug("exiting generateIV");
}
}
/**
* Generates the Key used for decryption and encryption using passphrase from properties file on library
*
** #param saltStr
* #return
* #throws EncryptionException
*/
private void generateKey(String saltStr) throws EncryptionException {
if (logger.isDebugEnabled()) {
logger.debug("entering generateKey");
}
try {
String passPhraseStr = rbConfig.getString("passphrase");
if (StringUtils.isEmpty(passPhraseStr)) {
throw new EncryptionException(_ERR_PASSPHRASE_IS_NULL);
}
if (StringUtils.isEmpty(saltStr)) {
throw new EncryptionException(_ERR_SALT_IS_NULL);
}
byte[] salt = DatatypeConverter.parseBase64Binary(saltStr);
SecretKeyFactory factory = SecretKeyFactory.getInstance(PBE_ALGORITHM);
SecretKey tmp = factory.generateSecret(new PBEKeySpec(passPhraseStr.toCharArray(), salt, ITERATIONS, KEY_LENGTH));
secretKey = new SecretKeySpec(tmp.getEncoded(), ALGORITHM_SHORT);
} catch (NoSuchAlgorithmException iae) {
logger.error(_ERR_NO_SUCH_ALGORITHM, iae);
throw new EncryptionException(_ERR_NO_SUCH_ALGORITHM, iae);
} catch (InvalidKeySpecException e) {
logger.error(_ERR_INVALID_KEY_SPEC, e);
throw new EncryptionException(_ERR_INVALID_KEY_SPEC, e);
}
if (logger.isDebugEnabled()) {
logger.debug("exiting generateKey");
}
}
/**
* Generates the Key used for decryption and encryption using passphrase passed in
*
* #param saltStr
* #param passPhrase
* #throws EncryptionException
*/
private void generateKey(String saltStr, String passPhrase) throws EncryptionException {
if (logger.isDebugEnabled()) {
logger.debug("entering generateKey");
}
try {
if (StringUtils.isEmpty(passPhrase)) {
throw new EncryptionException(_ERR_PASSPHRASE_IS_NULL);
}
if (StringUtils.isEmpty(saltStr)) {
throw new EncryptionException(_ERR_SALT_IS_NULL);
}
byte[] salt = DatatypeConverter.parseBase64Binary(saltStr);
SecretKeyFactory factory = SecretKeyFactory.getInstance(PBE_ALGORITHM);
SecretKey tmp = factory.generateSecret(new PBEKeySpec(passPhrase.toCharArray(), salt, ITERATIONS, KEY_LENGTH));
secretKey = new SecretKeySpec(tmp.getEncoded(), ALGORITHM_SHORT);
} catch (NoSuchAlgorithmException iae) {
logger.error(_ERR_NO_SUCH_ALGORITHM, iae);
throw new EncryptionException(_ERR_NO_SUCH_ALGORITHM, iae);
} catch (InvalidKeySpecException e) {
logger.error(_ERR_INVALID_KEY_SPEC, e);
throw new EncryptionException(_ERR_INVALID_KEY_SPEC, e);
}
if (logger.isDebugEnabled()) {
logger.debug("exiting generateKey");
}
}
/**
* This generates a random value of user defined size that can be used to encrypt data. The decrypting client will then need to access this value to be able to decrypt
*
* #return
*/
public static String generateSaltStr(int size) {
String salt = null;
byte[] newSeed = secureRandom.generateSeed(size);
salt = DatatypeConverter.printBase64Binary(newSeed);
return salt;
}
/**
* This generates a random 16 bit value that can be used to encrypt data. The decrypting client will then need to access this value to be able to decrypt
*
* #return
*/
public static String generateSaltStr() {
String salt = null;
byte[] newSeed = secureRandom.generateSeed(16);
salt = DatatypeConverter.printBase64Binary(newSeed);
return salt;
}
/**
* Loads properties files (call before attempting to read any properties files)
*/
private void loadProperties() {
try {
rbConfig = ResourceBundle.getBundle("application", Locale.US);
} catch (MissingResourceException ie) {
logger.fatal(
"FATAL: SecurityTools.loadProperties Cannot read the properties file. Please ensure the file is on the classpath.");
} catch (Exception e) {
logger.fatal(
"FATAL: SecurityTools.loadProperties Cannot read the properties file. Please ensure the file is on the classpath.");
}
}
/**
* Test method
*
* #param args
*/
public static void main(String[] args) {
String[] message = { "mkohanek", "9248547896548752345", "okok234234234okok467467",
"12" };
String result = null;
try {
// ASYMMETRIC TEST - use this block to test values are usable over separate sessions by taking output from loop below and using it here for subsequent calls
// String previousSalt = "jriUh+01HlHw4g3mO9PLcw==";
// SecurityTools asSecTool = new SecurityTools(previousSalt);
// String previousEncryptedValue = "nFVxkzdx+psiUVkCW5NztQ==";
// System.out.println("should decrypt to an expected value - " + asSecTool.decrypt(previousEncryptedValue));
// END ASYMMETRIC TEST
// SYMMETRIC TEST - tests that single requests will encrypt and decrypt successfully
String salt = generateSaltStr(16);
System.out.println("salt - " + salt);
SecurityTools secTool = new SecurityTools(salt);
for (String mess : message) {
result = secTool.encrypt(mess);
System.out.println(result);
result = secTool.decrypt(result);
System.out.println(result);
}
// END SYMMETRIC TEST
} catch (Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}
}
I created ciphertext with javax.crypto.Cipher org.apache.commons.codec.binary.Base64 and using Java like following:
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class PwdCipher {
private static final Logger LOG = LoggerFactory.getLogger(PwdCipher.class);
private static final int BASE64_ARG0 = 32;
private static final String SECRET = "tvnw63ufg9gh5392";
private static byte[] linebreak = {};
private static SecretKey key;
private static Cipher cipher;
private static Base64 coder;
static {
key = new SecretKeySpec(SECRET.getBytes(), "AES");
try {
cipher = Cipher.getInstance("AES/ECB/PKCS5Padding", "SunJCE");
} catch (NoSuchAlgorithmException | NoSuchProviderException | NoSuchPaddingException e) {
LOG.debug("Erro ao criar encriptador.", e);
}
coder = new Base64(BASE64_ARG0, linebreak, true);
}
private PwdCipher(){
}
public static synchronized String encrypt(String plainText) {
try {
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] cipherText = cipher.doFinal(plainText.getBytes());
return new String(coder.encode(cipherText));
} catch (Exception e) {
throw new GdocException("Erro ao encriptar senha.", e);
}
}
public static synchronized String decrypt(String codedText) {
try {
byte[] encypted = coder.decode(codedText.getBytes());
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decrypted = cipher.doFinal(encypted);
return new String(decrypted);
} catch (Exception e) {
throw new GdocException("Erro ao decriptar senha.", e);
}
}
}
I need to decrypt the text (encrypted with class PwdCipher) using Node.js, but when I use the following code:
exports.decryptLdapPassword = function(password){
var key = 'tvnw63ufg9gh5392';
var ciphertext = password;
var crypto = require('crypto');
var decipher = crypto.createDecipher('aes-128-ecb', key);
var decrypted = decipher.update(ciphertext, 'base64', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
};
It turns out the following exception:
> crypto.js:323 var ret = this._binding.final();
> ^ TypeError: error:00000000:lib(0):func(0):reason(0)
> at Decipher.Cipher.final (crypto.js:323:27)
> at Object.exports.decryptLdapPassword (C:\ProjetosTFS\GDOC\fSuporte\GDoc\Versionado\Fontes\woodstock\server\api\service\service.util.js:14:25)
> at Promise.<anonymous> (C:\ProjetosTFS\GDOC\fSuporte\GDoc\Versionado\Fontes\woodstock\server\api\service\service.controller.js:111:34)
> at Promise.<anonymous> (C:\ProjetosTFS\GDOC\fSuporte\GDoc\Versionado\Fontes\woodstock\node_modules\mongoose\node_modules\mpromise\lib\promise.js:177:8)
> at Promise.emit (events.js:95:17)
> at Promise.emit (C:\ProjetosTFS\GDOC\fSuporte\GDoc\Versionado\Fontes\woodstock\node_modules\mongoose\node_modules\mpromise\lib\promise.js:84:38)
> at Promise.fulfill (C:\ProjetosTFS\GDOC\fSuporte\GDoc\Versionado\Fontes\woodstock\node_modules\mongoose\node_modules\mpromise\lib\promise.js:97:20)
> at Promise.resolve (C:\ProjetosTFS\GDOC\fSuporte\GDoc\Versionado\Fontes\woodstock\node_modules\mongoose\lib\promise.js:114:23)
> at Promise.<anonymous> (C:\ProjetosTFS\GDOC\fSuporte\GDoc\Versionado\Fontes\woodstock\node_modules\mongoose\node_modules\mpromise\lib\promise.js:177:8)
> at Promise.emit (events.js:95:17)
Any idea?
Worked! Solution published in https://github.com/joyent/node/issues/20983#issuecomment-100885608
I'm trying to create a class that will allow me to encrypt and decrypt strings using the AES algorithm. I'm using the exception from http://aesencryption.net/#Java-aes-encryption-example but have modified the code to suit my needs.
This is my Main.java:
public class Main {
public static void main(String[] args) {
AES256 aes = new AES256();
aes.setKey("Secret Key");
String enc = "";
enc = aes.encrypt("qwertyuiopasdfgh");
System.out.println(enc);
System.out.println(aes.decrypt(enc));
}
}
And this is my AES256.java:
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
public class AES256 {
private SecretKeySpec secretKey;
private byte[] key;
public void setKey(String key) {
MessageDigest sha = null;
try {
this.key = key.getBytes("UTF-8");
sha = MessageDigest.getInstance("SHA-1");
this.key = sha.digest(this.key);
this.key = Arrays.copyOf(this.key, 16);
secretKey = new SecretKeySpec(this.key, "AES");
} catch (UnsupportedEncodingException | NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
public String getSecretKey() {
return secretKey.toString();
}
public String getKey() {
return new String(key);
}
public String encrypt(String string) {
try {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return Base64.getMimeEncoder().encodeToString(string.getBytes());
} catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException e) {
e.printStackTrace();
}
return null;
}
public String decrypt(String string) {
try {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return new String(cipher.doFinal(Base64.getMimeDecoder().decode(string.getBytes())));
} catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e) {
e.printStackTrace();
}
return null;
}
}
This is the error that is being thrown:
javax.crypto.BadPaddingException: Given final block not properly padded
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:966)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:824)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:436)
at javax.crypto.Cipher.doFinal(Cipher.java:2121)
at AES256.decrypt(AES256.java:55)
at Main.main(Main.java:13)
Does anybody know what is causing this error?
You return the original string in its base64-encoded form:
return Base64.getMimeEncoder().encodeToString(string.getBytes());
You'd want to use the cipher in there as well:
return Base64.getMimeEncoder().encodeToString(cipher.doFinal(string.getBytes()));
Independent of that, when depolying own crypto please be aware of the impacts of cipher modes, padding, etc. For example the ECB mode you're using will produce the same ciphertext from the same plaintext, e.g. the ciphertext might lead hints about the original text, as in the famous encrypted tux image:
Image Copyright: All uses are permitted provided that Larry Ewing, the owner of the original image, who requires that you mention him, his email address, lewing#isc.tamu.edu, and The GIMP, according to http://www.isc.tamu.edu/~lewing/linux/.
For more details on that, see Wikipedia's article about block cipher modes.
Here is my code:
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
public class EncryptedLogger {
private static Date lastLogTime = null;
private static EncryptedLogger instance = null;
private static FileOutputStream fos = null;
private static CipherOutputStream cos = null;
private static PrintWriter writer = null;
private Cipher cipher;
byte[] Key ={(byte) 0x12,(byte) 0x34,0x55,(byte) 0x66,0x67,(byte)0x88,(byte)0x90,0x12,(byte) 0x23,0x45,0x67,(byte)0x89,0x12,0x33,(byte) 0x55,0x74};
public static EncryptedLogger getInstance(){
if (instance==null) {
instance = new EncryptedLogger();
}
return instance;
}
private EncryptedLogger(){
class SQLShutdownHook extends Thread{
#Override
public void run() {
EncryptedLogger.close();
super.run();
}
}
SecretKeySpec sks = new SecretKeySpec(Key,"AES");
try {
cipher = Cipher.getInstance("AES/ECB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE,sks);
fos = new FileOutputStream(new File("log.txt"),true);
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
cos = new CipherOutputStream(fos, cipher);
writer = new PrintWriter(cos);
SQLShutdownHook hook = new SQLShutdownHook();
Runtime.getRuntime().addShutdownHook(hook);
}
public synchronized void logSQL(String s){
if ((lastLogTime==null)||((new Date().getTime() -lastLogTime.getTime())>1000)){
lastLogTime = new Date();
writer.printf("-- %1$tm-%1$te-%1$tY %1$tH-%1$tM-%1$tS\n%2$s\n",new Date(),s);
}
else{
writer.println(s);
}
}
public synchronized void logComment(String s){
writer.printf("-- %1$tm-%1$te-%1$tY %1$tH-%1$tM-%1$tS: %2$s\n",new Date(),s);
}
public static void close(){
writer.flush();
writer.close();
}
public static void main(String[] args) throws InterruptedException {
EncryptedLogger.getInstance().logSQL("1");
EncryptedLogger.getInstance().logSQL("22");
EncryptedLogger.getInstance().logSQL("33333");
EncryptedLogger.getInstance().logSQL("4900");
EncryptedLogger.getInstance().logSQL("5");
EncryptedLogger.getInstance().logSQL("66666");
EncryptedLogger.getInstance().logSQL("Some test logging statement");
EncryptedLogger.getInstance().logSQL("AAAAAAAAAAAAAAAAAAAAAAAAAA");
EncryptedLogger.getInstance().logComment("here is test commentary");
}
}
As you see i'm trying to encrypt text entries piping them through PrintWriter->CipherOutputStream->FileOutputStream chain. But when I decrypt result file there are missing bytes. I tried to flush cos and fos in EncryptedLogger.close() method - same result. Obviously i'm missing something. What is wrong?
EDIT: here is decryption code i use. It's not mine, taken from tutorial or something...
And it works fine when using simmilar encryption. But when using my code...
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
public class AESDecrypter
{
Cipher dcipher;
public AESDecrypter(SecretKey key)
{
try
{
dcipher = Cipher.getInstance("AES");
dcipher.init(Cipher.DECRYPT_MODE, key);
}
catch (Exception e)
{
e.printStackTrace();
}
}
byte[] buf = new byte[1024];
public void decrypt(InputStream in, OutputStream out)
{
System.out.println("decrypting");
try
{
in = new CipherInputStream(in, dcipher);
int numRead = 0;
while ((numRead = in.read(buf)) >= 0)
{
out.write(buf, 0, numRead);
}
out.close();
}
catch (java.io.IOException e)
{
}
}
public static void main(String args[])
{
try
{
byte[] keystr ={(byte) 0x12,(byte) 0x34,0x55,(byte) 0x66,0x67,(byte)0x88,(byte)0x90,0x12,(byte) 0x23,0x45,0x67,(byte)0x89,0x12,0x33,(byte) 0x55,0x74};
SecretKeySpec sks = new SecretKeySpec(keystr,"AES");
AESDecrypter encrypter = new AESDecrypter(sks);
encrypter.decrypt(new FileInputStream("sqllogenc.log"),new FileOutputStream("sqllogdec.log"));
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
EDIT2: when i write directly to fos i get this output:
-- 04-19-2012 16-17-56
1
22
33333
4900
5
66666 + delay starting 1100
Some test logging statement
AAAAAAAAAAAAAAAAAAAAAAAAAA
-- 04-19-2012 16-17-56: here is test commentary
and when writing using cos and decrypting:
-- 04-19-2012 16-22-13
1
22
33333
4900
5
66666 + delay starting 1100
Some test logging statement
AAAAAAAAAAAAAAAAAAAAAAAAAA
-- 04-19-2012 16-22-13: here
As you see part of the last line is missing including linebreak.
You should use the same cryptographic transformation (such as AES/ECB/NoPadding) at both sides. Also, note that NoPadding mode doesn't allow you to pass data of arbitrary size, therefore you need to specify some other kind of padding.
So, you need to construct Ciphers as Cipher.getInstance("AES/ECB/PKCS5Padding") at both sides.
Also, note the suggestion of rossum about use of CBC or CTR instead of ECB.
Well, AES has a fixed block size of 128 bits.
When you use AES/ECB/NoPadding, you take the responsability of making sure the size of your message is a multiple of the block size.
It probably isn't, so you get less text when you decrypt.
You should use AES/ECB/NoPadding for arbitrary length of text.