I have sensitive data which needs to be encrypted when saving in the database so that no one can read it even if they look into the database. So the approach I followed is to use AttributeConverter.
I created a class like this
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
#Converter
public class SensitiveDataConverter implements AttributeConverter<String, String> {
private final EncryptDecryptUtils encryptDecryptUtils;
/**
* Instantiates a new vulnerable data converter.
* Not using {#link javax.inject.Inject} because #Inject doesn't
* work with AttributeConverter
*
*/
public SensitiveDataConverter() {
this.encryptDecryptUtils = EncryptDecryptUtils.getInstance();
}
#Override
public String convertToDatabaseColumn(String attribute) {
return attribute != null ? encryptDecryptUtils.encrypt(attribute): null;
}
#Override
public String convertToEntityAttribute(String dbData) {
return dbData != null ? encryptDecryptUtils.decrypt(dbData): null;
}
And In my Entity class I have sensitive fields marked as
#Column
#Convert(converter = SensitiveDataConverter.class)
private String sensitiveData;
There are more than one column with such sensitive data.
My Encrypt/Decrypt code looks like this. I am using 128 length Key.
private static final byte[] IV = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
private static final IvParameterSpec IV_PARAMETER_SPEC = new IvParameterSpec(IV);
private static final int KEY_LENGTH = 128;
private final SecretKeySpec secretKeySpec;
private final Cipher cipher;
private static volatile EncryptDecryptUtils instance;
private String secretKey = "someSecretkey";
private String salt = "someSalt";
private EncryptDecryptUtils() {
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(secretKey.toCharArray(), salt.getBytes(StandardCharsets.UTF_8),
65536, KEY_LENGTH);
SecretKey tmp = factory.generateSecret(spec);
secretKeySpec = new SecretKeySpec(tmp.getEncoded(), "AES");
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
} catch (NoSuchAlgorithmException | InvalidKeySpecException | NoSuchPaddingException e) {
LOG.error("Error while encrypting: {}", e);
throw new EncryptDecryptException("Error while initializing encryption mechanism", e);
}
}
public static EncryptDecryptUtils getInstance() {
if (instance == null) {
synchronized (EncryptDecryptUtils .class) {
if (instance == null) {
instance = new EncryptDecryptUtils();
}
}
}
return instance;
}
public String encrypt(String stringToEncrypt){
try {
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, IV_PARAMETER_SPEC);
return Base64.getEncoder().encodeToString(cipher.doFinal(stringToEncrypt.getBytes(StandardCharsets.UTF_8)));
} catch (BadPaddingException | InvalidKeyException | IllegalBlockSizeException
| InvalidAlgorithmParameterException e) {
LOG.error("Error while encrypting: {}", stringToEncrypt, e);
throw new EncryptDecryptException("Error while encrypting sensitive data", e);
}
}
public String decrypt(String stringToDecrypt){
try {
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, IV_PARAMETER_SPEC);
return new String(cipher.doFinal(Base64.getDecoder().decode(stringToDecrypt)), StandardCharsets.UTF_8);
} catch (BadPaddingException | InvalidKeyException | IllegalBlockSizeException
| InvalidAlgorithmParameterException e) {
LOG.error("Error while decrypting: {}", stringToDecrypt, e);
throw new EncryptDecryptException("Error while decrypting sensitive data", e);
}
}
The whole code flow works perfectly fine. But every now and then while decrypting I see this exception being thrown
Caused by: javax.crypto.BadPaddingException: Invalid PKCS#5 padding length: <somenumber>
at iaik.security.cipher.f.b(Unknown Source)
at iaik.security.cipher.a.a(Unknown Source)
at iaik.security.cipher.a.engineDoFinal(Unknown Source)
at javax.crypto.Cipher.doFinal(Cipher.java:2164)
at com.bmw.scmaer.scenario.util.EncryptDecryptUtils.decrypt(EncryptDecryptUtils.java:xxx)
If I rerun the same flow again. It works. It is occuring intermittently. So I am not able to find out the root cause of this.
I am using JPA and eclipselink as ORM.
Any help is much appreciated.
Related
I had issue when converting AES encryption from java to php, it show different value result in php, that should show the exact value from java
Java
public class AesUtil {
private final Cipher cipher;
public final static String passPhrase = "";
public final static String IV = ""; //32bit IV Lenght
public final static String SALT = "";
public final static int KEY_SIZE = 128;
public final static int ITERATION_COUNT = 10000;
public AesUtil() {
try {
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
}
catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw fail(e);
}
}
//Call this function to encrypt some String
public String encrypt(String passPhrase, String plaintext) {
try {
SecretKey key = generateKey(SALT, passPhrase);
byte[] encrypted = doFinal(Cipher.ENCRYPT_MODE, key, IV, plaintext.getBytes("UTF-8"));
return base64(encrypted);
}
catch (UnsupportedEncodingException e) {
throw fail(e);
}
}
private byte[] doFinal(int encryptMode, SecretKey key, String iv, byte[] bytes) {
try {
cipher.init(encryptMode, key, new IvParameterSpec(hex(IV)));
return cipher.doFinal(bytes);
}
catch (InvalidKeyException
| InvalidAlgorithmParameterException
| IllegalBlockSizeException
| BadPaddingException e) {
throw fail(e);
}
}
private SecretKey generateKey(String salt, String passphrase) {
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(passphrase.toCharArray(), hex(salt), ITERATION_COUNT, KEY_SIZE);
SecretKey key = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
return key;
}
catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw fail(e);
}
}
public static String random(int length) {
byte[] salt = new byte[length];
new SecureRandom().nextBytes(salt);
return hex(salt);
}
public static String base64(byte[] bytes) {
return new String(Base64.encodeBase64(bytes));
}
public static byte[] base64(String str) {
return Base64.decodeBase64(str.getBytes());
}
public static String hex(byte[] bytes) {
return Hex.encodeHexString(bytes);
}
public static byte[] hex(String str) {
try {
return Hex.decodeHex(str.toCharArray());
}
catch (DecoderException e) {
throw new IllegalStateException(e);
}
}
private IllegalStateException fail(Exception e) {
return new IllegalStateException(e);
}
}
this is my code in php version,
<?php
$password = "";
$salt = "";
$iv = ""; //32bit IV Lenght
$length = 16;
$salt = "";
$interation = 10000;
$key1 = mb_convert_encoding($password, "UTF-8");
$bytes = openssl_pbkdf2($key1, $salt, $length, $interation, "sha1");
$bytes2 = hash_pbkdf2("sha1", $password, $salt, $interation, $length, true);
$ciphertext_b64 = base64_encode(openssl_encrypt($plaintext,"aes-128-cbc", $bytes2,OPENSSL_RAW_DATA, $iv));
?>
Please tell me how to solve this problem, if you have any suggestion in different programming language it's no problem, thank you
I am building an application that persists patient data. In order for the data to remain searchable, the identifiers and names need to be left un-encrypted(?). However I am planning to encrypt all other fields like address, phone, email, family members details and so on. I am using an AttributeConverter for this:
#Converter
public class AttributeEncryptor implements AttributeConverter<String, String> {
private static final String AES = "AES";
private static final byte[] encryptionKey = "big-secret".getBytes();
private final Cipher encryptCipher;
private final Cipher decryptCipher;
public AttributeEncryptor() throws Exception {
Key key = new SecretKeySpec(encryptionKey, AES);
encryptCipher = Cipher.getInstance(AES);
encryptCipher.init(Cipher.ENCRYPT_MODE, key);
decryptCipher = Cipher.getInstance(AES);
decryptCipher.init(Cipher.DECRYPT_MODE, key);
}
#Override
public String convertToDatabaseColumn(String attribute) {
try {
return Base64.getEncoder().encodeToString(encryptCipher.doFinal(attribute.getBytes()));
} catch (IllegalBlockSizeException | BadPaddingException e) {
throw new IllegalArgumentException(e);
}
}
#Override
public String convertToEntityAttribute(String dbData) {
try {
return new String(decryptCipher.doFinal(Base64.getDecoder().decode(dbData)));
} catch (IllegalBlockSizeException | BadPaddingException e) {
throw new IllegalArgumentException(e);
}
}
}
Is this the best approach? Are there other preferred / alternative options?
This is a good post I came across, discussing various options and their Pros/ Cons.
https://stackoverflow.com/a/43779197/5417843
I need to verify document with enveloped xml-dsig signature using java.security package.
After loading I unmarshal document and have object of Signature according to xsd - http://www.w3.org/2000/09/xmldsig#
Then:
#Service
public class XmlSignatureCheckerImpl implements XmlSignatureChecker {
private static final String ENCRYPTION_ALGORITHM = "RSA";
private static final String HASH_ENCRYPTION_ALGORITHM = "SHA1withRSA";
#Override
#Nullable
public PublicKey getPublicKey(byte[] exp, byte[] mod) {
BigInteger modulus = new BigInteger(1, mod);
BigInteger exponent = new BigInteger(1, exp);
RSAPublicKeySpec rsaPubKey = new RSAPublicKeySpec(modulus, exponent);
KeyFactory fact;
try {
fact = KeyFactory.getInstance(ENCRYPTION_ALGORITHM);
return fact.generatePublic(rsaPubKey);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
e.printStackTrace();
}
return null;
}
#Override
#Nullable
public Boolean verify(byte[] message, byte[] signature, PublicKey publicKey) {
final Signature sig;
try {
sig = Signature.getInstance(HASH_ENCRYPTION_ALGORITHM);
sig.initVerify(publicKey);
sig.update(message);
boolean verify = sig.verify(Base64.encodeBase64Chunked(signature));
return verify;
} catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException e) {
e.printStackTrace();
}
return null;
}
}
Call getPublicKey and verify, as a result I got signature length mismatch, if I did't encode signature I got no mismatch, but also verification is false, but I use test data which is completely valid. Give up with finding error, help me. please.
File encoding is UFT-8.
Have you look at official documentation? Seems like working with the sign factory is a bit more convenient http://www.oracle.com/technetwork/articles/javase/dig-signature-api-140772.html
Also, I've found these examples if it will be helpful https://www.java-tips.org/java-ee-tips-100042/158-xml-digital-signature-api/1473-using-the-java-xml-digital-signature-api.html
UPDATE: I removed another issue in my code to make the question more precise.
I need to encrypt a String with variable length with AES/CBC/NoPadding but I'm getting an IllegalBlockSizeException.
I have to use NoPadding because the input should have the same length as the output even if the decryption fails.
It shouldn't be possible to determine that it failed.
Before I used AES/CBC/PKCS5Padding without any problem but that is not an option. So my question is:
How do I add a custom padding to get a multiple of 16 byte or what possibly leads to the IllegalBlockSizeException (DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH)? I also read that ciphertext stealing is a way to do so. I would be grateful for an example.
Here's my current code:
private static final String KEY_TRANSFORMATION_ALGORITHM_SYM = "AES/CBC/NoPadding";
#NonNull
static String encryptMessage(#NonNull String plainMessage,
#NonNull SharedPreferences storage,
#Nullable Key aesKey,
#NonNull String charset) {
if (aesKey == null) {
throw new RuntimeException("AES key is null", null);
}
try {
// Cipher can not be re-used on Android
Cipher cipher = Cipher.getInstance(KEY_TRANSFORMATION_ALGORITHM_SYM);
cipher.init(Cipher.ENCRYPT_MODE, aesKey, new IvParameterSpec(getIV(storage, cipher, charset)));
byte[] charsetEncryptedData = cipher.doFinal(plainMessage.getBytes(charset));
return Base64.encodeToString(charsetEncryptedData, Base64.NO_WRAP);
} catch (NoSuchAlgorithmException | InvalidKeyException | NoSuchPaddingException | InvalidAlgorithmParameterException | BadPaddingException | IllegalBlockSizeException | UnsupportedEncodingException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
#NonNull
static String decryptMessage(#NonNull String encryptedMessage,
#NonNull SharedPreferences storage,
#Nullable Key aesKey,
#NonNull String charset) {
if (aesKey == null) {
throw new RuntimeException("AES key is null", null);
}
try {
//Cipher can not be re-used on Android
Cipher cipher = Cipher.getInstance(KEY_TRANSFORMATION_ALGORITHM_SYM);
cipher.init(Cipher.DECRYPT_MODE, aesKey, new IvParameterSpec(getIV(storage, cipher, charset)));
byte[] decryptedData = Base64.decode(encryptedMessage.getBytes(charset), Base64.NO_WRAP);
byte[] charsetEncryptedData = cipher.doFinal(decryptedData);
return new String(charsetEncryptedData, charset);
} catch (NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException | BadPaddingException | NoSuchPaddingException | IllegalBlockSizeException | UnsupportedEncodingException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
I solved my issue with the following code. I had to add a custom padding with spaces:
#NonNull
static String encryptMessage(#NonNull String plainMessage,
#NonNull SharedPreferences storage,
#Nullable Key aesKey,
#NonNull String charset) {
//...
// add spaces (custom padding) until the plainMessage.getBytes can be divided by 16 without rest --> this is the solution I was looking for
while (plainMessage.getBytes().length % 16 != 0) {
plainMessage += '\u0020';
}
//...
}
#NonNull
static String decryptMessage(#NonNull String encryptedMessage,
#NonNull SharedPreferences storage,
#Nullable Key aesKey,
#NonNull String charset) {
//...
// trim the String to get rid of the spaces
return new String(charsetEncryptedData, charset).trim();
//...
}
Google have provided the following example code showing how to generate a secure token for their second version of Recaptcha:
public class STokenUtils {
private static final String CIPHER_INSTANCE_NAME = "AES/ECB/PKCS5Padding";
public static final String createSToken(String siteSecret) {
String sessionId = UUID.randomUUID().toString();
String jsonToken = createJsonToken(sessionId);
return encryptAes(jsonToken, siteSecret);
}
private static final String createJsonToken(String sessionId) {
JsonObject obj = new JsonObject();
obj.addProperty("session_id", sessionId);
obj.addProperty("ts_ms", System.currentTimeMillis());
return new Gson().toJson(obj);
}
private static String encryptAes(String input, String siteSecret) {
try {
SecretKeySpec secretKey = getKey(siteSecret);
Cipher cipher = Cipher.getInstance(CIPHER_INSTANCE_NAME);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return BaseEncoding.base64Url().omitPadding().encode(cipher.doFinal(input.getBytes("UTF-8")));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static String decryptAes(String input, String key) throws Exception {
SecretKeySpec secretKey = getKey(key);
Cipher cipher = Cipher.getInstance(CIPHER_INSTANCE_NAME);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return new String(cipher.doFinal(
BaseEncoding.base64Url().omitPadding().decode(input)), "UTF-8");
}
private static SecretKeySpec getKey(String siteSecret){
try {
byte[] key = siteSecret.getBytes("UTF-8");
key = Arrays.copyOf(MessageDigest.getInstance("SHA").digest(key), 16);
return new SecretKeySpec(key, "AES");
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
}
The full code can be found at: https://github.com/google/recaptcha-java
I'm wanting to generate this token in Ruby 2.1+ and have got this far but it outputs incorrect data. I'm trying to slowly debug it, but in the meantime I'm wondering if anyone can see any obvious flaws in my process?
stoken_json = hash_to_json({'session_id' => SecureRandom.uuid, 'ts_ms' => Time.now.to_i})
cipher = OpenSSL::Cipher::AES128.new(:ECB)
private_key_digest = Digest::SHA1.hexdigest(private_key)[0...16]
cipher.encrypt
cipher.key = private_key_digest
encrypted_stoken = cipher.update(stoken_json) << cipher.final
encoded_stoken = Base64.urlsafe_encode64(encrypted_stoken).gsub(/\=+\Z/, '')
Turns out I was close. I needed to digest not hexdigest the private key:
private_key_digest = Digest::SHA1.digest(private_key)[0...16]
So the final code is:
stoken_json = hash_to_json({'session_id' => SecureRandom.uuid, 'ts_ms' => (Time.now.to_f * 1000).to_i})
cipher = OpenSSL::Cipher::AES128.new(:ECB)
private_key_digest = Digest::SHA1.digest(private_key)[0...16]
cipher.encrypt
cipher.key = private_key_digest
encrypted_stoken = cipher.update(stoken_json) << cipher.final
encoded_stoken = Base64.urlsafe_encode64(encrypted_stoken).gsub(/\=+\Z/, '')
There didn't seem to be a built-in way to strip the padding from the base64 string, thus the .gsub at the end.
I also needed the timestamp in milliseconds so that part has been modified too.
In the recaptcha gem there is a method hash_to_json that I'm using, otherwise I suspect you'd use the JSON gem.