I have an application which may require a password for authentification.
This application does not handle any sensitive data, because this "password" is chosen by the host and told the "clients" via another channel (WhatsApp or whatever).
So when a client wants to authenticate, the host generates a random String which is sent to the client.
The client then encrypts this Random String with the password the user enters.
The encrypted random String is the sent back t the host.
The host decrypts this encrypted String with the key generated by the same password.
If the unencrypted and original String match, the user is logged in.
This is what I came up with so far:
String base64;
char[] password = "password".toCharArray();
String randomString = new BigInteger(130, new SecureRandom()).toString(32);
try {
//Encrypt Client Side
SecretKey key = new SecretKeySpec(SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512").generateSecret(new PBEKeySpec(password)).getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
base64 = Base64.getEncoder().encodeToString(cipher.doFinal(randomString.getBytes(StandardCharsets.UTF_8)));
} catch (GeneralSecurityException e) {
throw new IllegalStateException(e);
}
try {
//Decrypt Server Side
SecretKey key = new SecretKeySpec(SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512").generateSecret(new PBEKeySpec(password)).getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, key);
//Check if both strings match
System.out.println(Arrays.equals(cipher.doFinal(Base64.getDecoder().decode(base64)), randomString.getBytes(StandardCharsets.UTF_8)));
} catch (GeneralSecurityException e) {
throw new IllegalStateException(e);
}
Unfortunately this code throws an exception: java.security.spec.InvalidKeySpecException: Salt not found
Should I use a different algorithm, or should I generate a salt by hashing the password itself, or a completely different approach?
I'd like to avoid having to send a generated salt along with the random String
You should give PBEKeySpec what it needs to generate enough bits for an AES key. You need the same salt on both sides, so you could do:
byte[] salt = new byte[8];
System.arraycopy(randomString.getBytes("UTF-8"), 0, salt, 0, 8);
Now replace your PBEKeySpec with new PBEKeySpec(password, salt, 10, 128) and everything should work.
Related
Currently, I'm having a problem that I don't know how to solve. It's the decryption and encryption of the string using AES256. Everything was working fine until I restarted the server and I couldn't decode the previous data.
I tried saving the salt and IVParameter to decrypt for next time, but it doesn't work.
private static final String SECRET_KEY = "my_key";
private static final byte[] SALT;
private static final SecureRandom random;
private static final IvParameterSpec ivspec;
static {
random = new SecureRandom();
SALT = new byte[16];
random.nextBytes(SALT);
byte[] bytesIV = new byte[16];
random.nextBytes(bytesIV);
ivspec = new IvParameterSpec(bytesIV);
}
public static String encrypt(String stringToEncrypt) {
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(SECRET_KEY.toCharArray(), SALT, 65536, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKeySpec secretKey = new SecretKeySpec(tmp.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivspec);
return Base64.getEncoder().encodeToString(cipher.doFinal(strToEncrypt.getBytes(StandardCharsets.UTF_8)));
} catch (Exception e) {
System.out.println("Error while encrypting: " + e);
}
return null;
}
public static String decrypt(String stringToDecrypt) {
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(SECRET_KEY.toCharArray(), SALT, 65536, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKeySpec secretKey = new SecretKeySpec(tmp.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivspec);
return new String(cipher.doFinal(Base64.getDecoder().decode(strToDecrypt)));
} catch (Exception e) {
System.out.println("Error while decrypting: " + e);
}
return null;
}
SecureRandom initialises itself differently everytime you instantiate it. I.e., it will also create a different sequence of random values each time. Even if you initialise SALT with a fixed initial value, in the next step you overwrite it again by calling random.nextBytes(SALT). Either don't do that or instantiate SecureRandom with a seed, so it creates the same sequence of random numbers every time. But this is kind of counter-productive. Similarly, you also randomise IvParameterSpec.
You only need the nextBytes() result, if you want to generate new salt or IV values for a multiple users or a sequence of distinct encryption/decryption actions. AES being a symmetric cypher, you need to make sure that when decrypting a message, you use the same salt and IV (if any) which were used for encryption. Try this in order to get identical encryption results:
static {
random = new SecureRandom(); // not used in this example
SALT = "I am so salty!".getBytes(StandardCharsets.UTF_8);
byte[] bytesIV = "my super fancy IV".getBytes(StandardCharsets.UTF_8);
ivspec = new IvParameterSpec(Arrays.copyOfRange(bytesIV, 0, 16));
}
Of course, in the example above I am assuming that actually salt and IV were initially created randomly, then securely saved or transmitted to the recipient, and then loaded/received and used to decrypt the message. In a real-world scenario, you would transmit or store salt and IV asymmetrically encrypted (using public-key cryptography), while the message itself (which usually is much bigger than secret key, salt and IV) is encrypted using the much faster and more efficient symmetric AES256 algorithm.
P.S.: The Arrays.copyOfRange(bytesIV, 0, 16) is necessary, because in contrast to the salt the IV must be exactly 16 bytes long. The salt is more flexible.
Update: Actually, it is not necessary to encrypt salt and IV. They just make sure that the same input and secret key do not yield the same encrypted message in order to make attacks based on known cleartext more difficult. This is also why e.g. when storing salted hashes in a database, you store the salt values as cleartext along with the salted password hash (not the password itself!), because you need them every time you want to validate a user password.
Hi I'm a new user and this is my first question:
I state that I have no extensive knowledge of cryptography.
I'm trying to encrypt files with a user-supplied password and I have found this method:
fileProcessor(Cipher.ENCRYPT_MODE,key,inputFile,newFile);
static void fileProcessor(int cipherMode,String key,File inputFile,File outputFile) {
try {
Key secretKey = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(cipherMode, secretKey);
FileInputStream inputStream = new FileInputStream(inputFile);
byte[] inputBytes = new byte[(int) inputFile.length()];
inputStream.read(inputBytes);
byte[] outputBytes = cipher.doFinal(inputBytes);
FileOutputStream outputStream = new FileOutputStream(outputFile);
outputStream.write(outputBytes);
inputStream.close();
outputStream.close();
} catch (NoSuchPaddingException | NoSuchAlgorithmException
| InvalidKeyException | BadPaddingException
| IllegalBlockSizeException | IOException e) {
e.printStackTrace();
}
}
The problem is that the program only works if I enter a 16 byte password (I think even a multiple of it is fine).
How can I use a password that is not necessarily a multiple of 16 bytes?
A key (SecretKeySpec) is a cryptographic key and not a simple plaintext password supplied by user. AES standard specifies the following key sizes: 128, 192 or 256 bits.
A key can be created from a text password using a key derivation function, for example PBKDF2.
As Maarten-reinstateMonica mentioned in the comment, Cipher.getInstance("AES") results in AES encryption in ECB mode that is insecure. AES-GCM is strong approved authenticated encryption modes based on AES algorithm.
Also, you need to understand the following concepts before proceeding to the sample code:
salting
PBKDF2 password hasing algorithm
what is confidentiality, integrity and authenticity in Information security
password strength recommendations
Sample code:
// The number of times that the password is hashed during the derivation of the symmetric key
private static final int PBKDF2_ITERATION_COUNT = 300_000;
private static final int PBKDF2_SALT_LENGTH = 16; //128 bits
private static final int AES_KEY_LENGTH = 256; //in bits
// An initialization vector size
private static final int GCM_NONCE_LENGTH = 12; //96 bits
// An authentication tag size
private static final int GCM_TAG_LENGTH = 128; //in bits
private static byte[] encryptAES256(byte[] input, String password) {
try {
SecureRandom secureRandom = SecureRandom.getInstanceStrong();
// Derive the key, given password and salt
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
// A salt is a unique, randomly generated string
// that is added to each password as part of the hashing process
byte[] salt = new byte[PBKDF2_SALT_LENGTH];
secureRandom.nextBytes(salt);
KeySpec keySpec =
new PBEKeySpec(password.toCharArray(), salt, PBKDF2_ITERATION_COUNT, AES_KEY_LENGTH);
byte[] secret = factory.generateSecret(keySpec).getEncoded();
SecretKey key = new SecretKeySpec(secret, "AES");
// AES-GCM encryption
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
// A nonce or an initialization vector is a random value chosen at encryption time
// and meant to be used only once
byte[] nonce = new byte[GCM_NONCE_LENGTH];
secureRandom.nextBytes(nonce);
// An authentication tag
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, nonce);
cipher.init(Cipher.ENCRYPT_MODE, key, gcmParameterSpec);
byte[] encrypted = cipher.doFinal(input);
// Salt and nonce can be stored together with the encrypted data
// Both salt and nonce have fixed length, so can be prefixed to the encrypted data
ByteBuffer byteBuffer = ByteBuffer.allocate(salt.length + nonce.length + encrypted.length);
byteBuffer.put(salt);
byteBuffer.put(nonce);
byteBuffer.put(encrypted);
return byteBuffer.array();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static byte[] decryptAES256(byte[] encrypted, String password) {
try {
// Salt and nonce have to be extracted
ByteBuffer byteBuffer = ByteBuffer.wrap(encrypted);
byte[] salt = new byte[PBKDF2_SALT_LENGTH];
byteBuffer.get(salt);
byte[] nonce = new byte[GCM_NONCE_LENGTH];
byteBuffer.get(nonce);
byte[] cipherBytes = new byte[byteBuffer.remaining()];
byteBuffer.get(cipherBytes);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
KeySpec keySpec =
new PBEKeySpec(password.toCharArray(), salt, PBKDF2_ITERATION_COUNT, AES_KEY_LENGTH);
byte[] secret = factory.generateSecret(keySpec).getEncoded();
SecretKey key = new SecretKeySpec(secret, "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
// If encrypted data is altered, during decryption authentication tag verification will fail
// resulting in AEADBadTagException
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, nonce);
cipher.init(Cipher.DECRYPT_MODE, key, gcmParameterSpec);
return cipher.doFinal(cipherBytes);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws Exception {
String password = "Q8yRrM^AvV5r8Yx+"; //Password still has to be strong ehough
String input = "Sample text to encrypt";
byte[] encrypted = encryptAES256(input.getBytes(UTF_8), password);
System.out.println(Base64.getEncoder().encodeToString(encrypted));
//s+AwwowLdSb3rFZ6jJlxSXBvzGz7uB6+g2e97QXGRKUY5sHPgf94AOoybkzuR3rNREMj56Ik1+Co682s4vT2sAQ/
byte[] decrypted = decryptAES256(encrypted, password);
System.out.println(new String(decrypted, UTF_8));
//Sample text to encrypt
}
A few more words about random nonces. If only a few records are encrypted with the same key, then a random nonce does not pose a risk. However, if a large number of records is encrypted with the same key, the risk may become relevant.
A single repeated nonce is usually enough to fully recover the
connection’s authentication key. In such faulty implementations,
authenticity is lost and an attacker is able to manipulate
TLS-protected content.
For safety reasons random nonces should be avoided and a counter should be used.
I'm trying to encrypt files with a user-supplied password
How can I use a password that is not necessarily a multiple of 16 bytes?
To create an encryption key from a user provided password you may check some examples, generally search for "password based encryption"
Here is en example how to create an encryption key using a user password
private static final String PBKDF_ALG = "PBKDF2WithHmacSHA256";
private static final int PBKDF_INTERATIONS = 800000;
// create key from password
SecretKeyFactory secKeyFactory = SecretKeyFactory.getInstance(PBKDF_ALG);
KeySpec pbeSpec = new PBEKeySpec(password.toCharArray(), psswdSalt, PBKDF_INTERATIONS, SYMMETRIC_KEY.length*8);
SecretKey pbeSecretKey = secKeyFactory.generateSecret(pbeSpec);
SecretKey secKey = new SecretKeySpec(pbeSecretKey.getEncoded(), SYMMETRIC_KEY_ALG);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(cipherMode, secKey) ;
I am encrypting a string using RSA algorithm and encryption and decryption logic is
public class RsaEncrypt {
private static final String ALGORITHM = "RSA";
public static void main(String[] args) {
String filePath = "/home/Desktop/abc.jks";
char[] password = "changeit".toCharArray();
String alias = "123";
KeyStore ks = null;
try {
//loading the keystore
ks = KeyStore.getInstance("JKS");
InputStream readStream = new FileInputStream(filePath);
ks.load(readStream, password);
Certificate cert = ks.getCertificate(alias);
PublicKey publicKey = cert.getPublicKey();
PrivateKey privateKey = (PrivateKey) ks.getKey(alias, password);
RsaEncrypt e = new RsaEncrypt();
String result = e.encrypt("abvhdh", publicKey);
String decryptResult = e.decrypt(result.getBytes(), privateKey);
} catch (Exception e) {
e.printStackTrace();
}
}
//Encryption of a string
public String encrypt(String text,PublicKey publicKey) {
String retVal = null;
byte[] cipherText = null;
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
cipherText = cipher.doFinal(text.getBytes());
cipherText = Base64.getEncoder().encode(cipherText);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
return new String(cipherText) ;
}
// Decryption of a string
private String decrypt(byte[] text, PrivateKey privatekey) {
byte[] dectyptedText = null;
try {
final Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, privatekey);
dectyptedText = cipher.doFinal(Base64.getDecoder().decode(text));
} catch (Exception ex) {
ex.printStackTrace();
} catch (Throwable e) {
throw new RuntimeException(e);
}
return new String(dectyptedText);
}
The Result is fine.But if i encrypt same string one more time i am getting different encryption value.
For that i am using "RSA/ECB/NoPadding" cipher instead of "RSA",then If i encrypt one string many times i am getting same encryption value.
But when i decrypt,The reuslt string contains some null characters
Example
input : abcd output : abcd \u0000 \u0000 \u0000 \u0000....
How can i resolve this problem and what is the best way to get same encryption value if we encrypt multiple times?????
The Result is fine.But if i encrypt same string one more time i am getting different encryption value.
That is correct and it is even required property of the RSA encryption. By default PKCS1.5 padding is used (RSA/ECB/PKCS1Padding) which contains some random bytes. Safer option is RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING mode which is even more random.
For that i am using "RSA/ECB/NoPadding" cipher instead of "RSA",then If i encrypt one string many times i am getting same encryption value. But when i decrypt,The reuslt string contains some null characters
Using RSA without padding (NoPadding) is very unsafe (it is called textbook RSA).
Padding extends the original encrypted value to full space length (e.g. 2048 bits) and then the RSA magic (exponentiation) will be executed. Using the NoPadding parameter you are telling the crypto library that you will do the padding yourself. In that case you are expected to remove the padding after decryption (in your case zero padding)
I hope you are doing that for learning / academic purposes, not some real security project. You may have a look at my blog about encryption to get some examples.
btw: you should not use RSA to encrypt the plaintext itself. Rather use symmetric encryption to encrypt the plaintext and then RSA to encrypt the symmetric encryption key.
I am doing a AES encryption , in which i will use a secret key from cert file as below to initialise the cipher.
encryptModeCipher = Cipher.getInstance("AES");
encryptModeCipher.init(Cipher.ENCRYPT_MODE, aesSecretKey);
But the problem i see here is that, my secretKey () remains the same for all the certificates that i use. Any suuggestion why? and suggest a good idea to do so.
byte[] encryptionKey = Arrays.copyOf(encoded, 32);
secretKey = new SecretKeySpec(encryptionKey, algorithm);
public class AESEncryptionServiceHelper {
private String algorithm = "AES";
private String certPass;
private SecretKey secretKey;
public SecretKey setKey() {
try {
certPass="****";
char[] pass = certPass.toCharArray();
KeyStore keyStore = KeyStore.getInstance("jceks");
File file = new File("D:/aws-kms-dps/***.jks");
InputStream inputStream = new FileInputStream(file);
keyStore.load(inputStream, pass);
Certificate cert = keyStore.getCertificate("****");
Key key = cert.getPublicKey();
secretKey = new SecretKeySpec(key.getEncoded(), algorithm);
byte[] encoded = secretKey.getEncoded();
byte[] encryptionKey = Arrays.copyOf(encoded, 32);
secretKey = new SecretKeySpec(encryptionKey, algorithm);
} catch (IOException e) {
System.out.println(e);
} catch (Exception e) {
System.out.println(e);
}
return secretKey;
}
public static void main(String args[]){
AESEncryptionServiceHelper aesEncryptionServiceHelper=new AESEncryptionServiceHelper();
aesEncryptionServiceHelper.setKey();
}
}
You seems you are usging (part of) the public key as an AES key. That is VERY BAD idea as
the public key is .. well .. public and static
it has relatively low entropy (as multiple bytes are defined in the ASN.1 format)
Did you do any research how to properly do encryption using PKI or you are just guessing / plaing with the crypto API?
Let's assume you want to do encryption using the public key and AES (it is called hybrid encryption), you could take example from my blog
Please read it and understand (or any other good blogs about cryptography), seems you are missing using IV (salt) and MAC
// generate random AES key
KeyGenerator keyGenerator = KeyGenerator.getInstance(SYMMETRIC_KEY_ALG);
SecretKey symmetricKey = keyGenerator.generateKey();
// this assumes there's whole keypair (including private key)
// normally only a certificate with PubKey is available
PublicKey pubKey = keystoreEntry.getCertificate().getPublicKey();
params.setKey(symmetricKey.getEncoded());
// execute symmetric encryption
this.symmetricEncryption(params);
// encrypt the key with the public key
Cipher cipher = Cipher.getInstance(PKI_CIPHER_ALG);
cipher.init(Cipher.WRAP_MODE, pubKey);
byte[] wrappedKey = cipher.wrap(symmetricKey);
LOGGER.log(Level.INFO, "Wrapped key: {0}", Base64.getEncoder().encodeToString(wrappedKey));
params.setKey(wrappedKey);
where the symetric encryption itself can be implemented as follows
// initialization vector
SecureRandom rnd = new SecureRandom();
byte[] iv = new byte[SYMMETRIC_BLOCK_SIZE / 8];
rnd.nextBytes(iv);
encryptionParams.setIv(iv);
IvParameterSpec ivParamSpec = new IvParameterSpec(iv);
SecretKey symmetricKey = new SecretKeySpec(encryptionParams.getKey(), SYMMETRIC_KEY_ALG);
Cipher cipher = Cipher.getInstance(SYMMETRIC_CIPHER_NAME);
cipher.init(Cipher.ENCRYPT_MODE, symmetricKey, ivParamSpec);
// for HMAC we should be able to use the same key as for encryption
// for CBC-MAC it may not be the case
// https://en.wikipedia.org/wiki/CBC-MAC#Using_the_same_key_for_encryption_and_authentication
Mac mac = Mac.getInstance(EncryptionTest.HASH_ALGORITHM_NAME);
mac.init(symmetricKey);
byte[] encrypted = cipher.doFinal(encryptionParams.getPlaintext());
encryptionParams.setCiphertext(encrypted);
byte[] authTag = mac.doFinal(encrypted);
encryptionParams.setMac(authTag);
I have RSA Public-Key on server side in below format:
<string xmlns="http://www.cherripik.com/">
<RSAKeyValue><Modulus>abc</Modulus><Exponent>abc</Exponent></RSAKeyValue>
</string>
I have tried almost all possible ways but could not able to encrypt string with this public key on android side. Could anyone give me an example in which i will encrypt any of the string like "abc" with this public key and also decrypt that encrypted key to back to "abc". It will be very helpful to me.
Advance Thanks.
Below are the ways which i have used but no success. It gave some value but it is not correct.
public String encrypt(String message, String Modulus, String Exponent) {
String outputEncrypted = "";
try {
byte[] modulusBytes = Base64Coder.decode(Modulus);
byte[] exponentBytes = Base64Coder.decode(Exponent);
BigInteger modulus = new BigInteger(modulusBytes );
BigInteger exponent = new BigInteger(exponentBytes);
RSAPublicKeySpec rsaPubKey = new RSAPublicKeySpec(modulus, exponent);
KeyFactory fact = KeyFactory.getInstance("RSA");
PublicKey pubKey = fact.generatePublic(rsaPubKey);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] plainBytes = new String("abc").getBytes("UTF-8");
byte[] cipherData = cipher.doFinal( plainBytes );
String encryptedString = new String(Base64Coder.encode(cipherData));
Log.i(this.getClass().getSimpleName(), "encryptedString : "+encryptedString);
} catch (Exception e) {
// TODO: handle exception
}
return outputEncrypted;
}
One more thing when I create encrypted string with above method. It will give 346 characters encrypted string. But on my server, I have only encrypt and decrypt method. On server encrypt method, it will producing 344 character. Lastly when I put my encrypted string to server method to verify that my encrypted string is correct. Server throw this error.
<string xmlns="http://www.Myserver.com/">Error occurred while decoding OAEP padding.</string>