Encrypting/Decrypting with AES and storing information in text file - java

I have been working on my own little project, where I am trying to make a simple password manager. The problem I'm having currently, is getting it to work in a way so that when ran, it saves the encrypted password to a file, then when ran another time, you can call it and it will be decrypted, showing you your password for the username you call.
For something I would like to add on to the program later, I do need to keep the encryption/decryption methods separate.
The current error is:
Exception in thread "main" javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
Any help is greatly appreciated.
Code follows:
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import java.util.Scanner;
import javax.crypto.spec.SecretKeySpec;
public class PasswordManager3
{
static String key = "SimplePasswordMg";
static String password1 = "";
static String password2 = "";
static String username = "";
public static void main(String[] args)
throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException,
BadPaddingException, IOException
{
Key aesKey = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");
System.out.println("Enter New to input a new password, or Retrieve to retrieve an old password:");
Scanner scanner1 = new Scanner(System.in);
String answer = scanner1.nextLine();
if(answer.equalsIgnoreCase("New")) {
System.out.println("Please enter a username: ");
Scanner scanner2 = new Scanner(System.in);
username = scanner2.nextLine();
System.out.println("Please enter a password: ");
Scanner scanner3 = new Scanner(System.in);
password1 = scanner3.nextLine();
System.out.println("Please enter your password again: ");
Scanner scanner4 = new Scanner(System.in);
password2 = scanner4.nextLine();
if (password1.equalsIgnoreCase(password2)) {
Files.write(Paths.get(username + ".txt"), encrypt(password1, cipher, aesKey));
System.out.println("Your password has been stored.");
}
else {
System.out.println("The passwords you entered did not match. Exiting password manager.");
}
}
else if(answer.equalsIgnoreCase("Retrieve")) {
System.out.println("Please enter the username you would like to retrieve the password for: ");
Scanner scanner5 = new Scanner(System.in);
username = scanner5.nextLine();
BufferedReader in = new BufferedReader(new FileReader(username + ".txt"));
String encryptedpass = in.readLine();
byte[] encryptedpass2 = encryptedpass.getBytes("UTF-8");
System.out.println(decrypt(encryptedpass2, cipher, aesKey));
}
else {
System.out.println("You entered an incorrect option, program exited.");
}
}
public static byte[] encrypt(String str, Cipher cipher, Key aesKey)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException
{
cipher.init(Cipher.ENCRYPT_MODE, aesKey);
byte[] encrypted = cipher.doFinal(key.getBytes("UTF-8"));
return encrypted;
}
public static String decrypt(byte[] byte1, Cipher cipher, Key aesKey)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException
{
cipher.init(Cipher.DECRYPT_MODE, aesKey);
String decrypted = new String(cipher.doFinal(byte1));
return decrypted;
}
}

You are not writing a text file. Encrypted data is effectively random bits, and your main passes the return from encrypt directly to Files.write(Path,byte[]) which writes it as binary.
When you read it back in with FileReader it uses the default encoding for your platform, which you didn't identify, and sometimes your user's environment, which may or may not mangle some bytes; using readLine() may discard part of the data, and encoding it with getBytes("UTF-8") when it was never valid characters to start with is about 99.6% certain to mangle whatever is left. As a result the value you pass to decrypt is completely wrong, and can't be decrypted.
The easy, symmetric fix is use File.readAllBytes(Path) to read the (whole) file as binary, and decrypt the byte[] value that returns.
Alternatively, if you really want text files for some reason (and I don't see any), you need to first encode the encrypted value into a textual form and write that, probably with a line terminator added, then read it back (as a line if you chose that) and decode it before decrypting. Base64 and hexadecimal (abbreviated hex) are the two most common methods of textually encoding binary data.
Also: using a key which is all printable ASCII and contains even parts of English words hugely weakens your encryption, from the nominal 128 bits to something more like 20-30 bits, which can be easily broken by any half-competent attacker. Using any hardcoded key is also a danger, although this is a harder problem and there is no single, easy and good solution.
And you are using AES in ECB mode by default. Using ECB for passwords (and almost anything else) is a bad idea; to learn why google "ECB penguin" and "Adobe password breach" and/or see
https://crypto.stackexchange.com/questions/11451/can-ecb-mode-really-leak-some-characters
https://crypto.stackexchange.com/questions/11456/what-does-it-mean-if-second-half-of-8-char-string-encrypted-in-3des-is-always-id

Related

Hashing vulnerability

I want to add some security to my project so I added a password field. In, order to store the password I was going to use a txt and save it in there, to add a bit more security I used the below code to hash the password(theirs more than one password saved this way if that's important). This is just and example of how I have done the hashing, the actual program uses text files etc.
public static void main(String[] args) throws NoSuchAlgorithmException {
System.out.println("Enter Password: ");
Scanner scanner = new Scanner(System.in);
String enteredPassword = scanner.nextLine();
String storedPassword = "�D�Ϛ-�UK�c�=�,�}��}��D��Zj>�m";
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(enteredPassword.getBytes());
String hashedString = new String(messageDigest.digest());
System.out.println(hashedString);
if(storedPassword.equals(hashedString)){
System.out.println("Passwords Match!");
}else{
System.out.println("Passwords Do Not Match!");
}
}
My question is am I doing this securely, besides decompiling my project and bypassing this feature is my project secure or can this method be exploited? Also, is there a way to secure a project against being decompiled and the code re-written to bypass security features?? Thank You
The approach itself is good; SHA-256 by itself is a strong, one-way hashing function. It cannot be "decrypted". But it's fast, thus allowing rapid brute-forcing of the password using a dictionary.
For better security you can slow things down with e.g. bcrypt or PBKDF2. Some 100ms will not be noticeable by the user, but makes brute-forcing impractical.
Here's an example with PBKDF2 using 100000 iterations of SHA-256. It also uses a random salt.
SecureRandom random = SecureRandom.getInstanceStrong();
byte[] salt = new byte[16];
random.nextBytes(salt);
KeySpec spec = new PBEKeySpec("my-secret-password".toCharArray(), salt, 100000, 256);
SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
byte[] hash = f.generateSecret(spec).getEncoded();
Base64.Encoder enc = Base64.getEncoder();
System.out.printf("salt: %s%n", enc.encodeToString(salt));
System.out.printf("hash: %s%n", enc.encodeToString(hash));
Note: PBKDF2WithHmacSHA256 is available since Java 8.
Here's a more complete example:
private static final SecureRandom random = new SecureRandom();
/**
* One-way encrypts (hashes) the given password.
*
* #param saltpw the salt (will be generated when null)
* #param pw the password to encrypt
* #return encrypted salted password
*/
public static String encrypt(String saltpw, String pw) throws GeneralSecurityException {
byte[] salt;
if (saltpw == null) {
salt = new byte[16];
random.nextBytes(salt);
} else {
salt = Base64.getDecoder().decode(saltpw.replaceFirst("\\$.*", ""));
}
KeySpec spec = new PBEKeySpec(pw.toCharArray(), salt, 100000, 256);
SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
byte[] hash = f.generateSecret(spec).getEncoded();
Base64.Encoder enc = Base64.getEncoder();
return enc.encodeToString(salt) + "$" + enc.encodeToString(hash);
}
public static void main(String[] args) throws Exception {
String enc = encrypt(null, "my-secret-password");
System.out.printf("enc : %s\n", enc);
String test1 = encrypt(enc, "my-secret-password");
System.out.printf("test 1: %s, valid: %b\n", test1, enc.equals(test1));
String test2 = encrypt(enc, "some-other-password");
System.out.printf("test 2: %s, valid: %b\n", test2, enc.equals(test2));
}
Prints:
enc : B5V6SjkjJpeOxvMAkPf7EA==$NNDA7o+Dpd+M+H99WVxY0B8adqVWJHZ+HIjgPxMljwo=
test 1: B5V6SjkjJpeOxvMAkPf7EA==$NNDA7o+Dpd+M+H99WVxY0B8adqVWJHZ+HIjgPxMljwo=, valid: true
test 2: B5V6SjkjJpeOxvMAkPf7EA==$4H1SpH8N+/jqU40G6RWb+ReHUB3C58iAaU4l39j+TV8=, valid: false
Notice how test 1 results in exactly the same encrypted string as the original password, and that test 2 (with a wrong password) doesn't. So that's how you can verify that the provided password is valid or not, by just comparing the hashes.
There is no way to prohibit decompiling of java.
But you can make it hard to understand the decompiled code if you use an obfuscator.
E.g. https://www.guardsquare.com/en/products/proguard
This changes all you method-, class-, variable- names to meaningless short names.
A side-effect is that your class file will shrink too.

Problem with java Cipher when i want to decipher it

I have made an app with javafx that I can write something and save it to database. my database is sqlite. it's a very simple app. although I've added login app to my writing app, still the sqlite can be opened by any software.
instead of encrypting the sqlite db(which i didn't know and i found really confusing to do :) ) I decided to encrypt the text in java and later when i want to read it i would turn it back to normal and show it.
I learned how to do it from this link and i changed it to print the string instead of writing to a file
so my final code looks like this:
public static void main(String[] args) throws Exception {
String textA = "";
String textB="";
byte[] thisismykey = "Hello How manyBytes are in#hts A".getBytes();
SecretKey secKey = new SecretKeySpec(thisismykey, "AES");
Cipher aesCipher = Cipher.getInstance("AES");
//turn your original text to byte
byte[] myoriginaltexttobyte = "Your Plain Text Here".getBytes();
//activate the encrypt method
aesCipher.init(Cipher.ENCRYPT_MODE, secKey);
//encrypt the text and assign the encrypted text to a byte array
byte[] bytecipheredoforgtext = aesCipher.doFinal(myoriginaltexttobyte);
//change it to string with new string
textA = new String(bytecipheredoforgtext);
System.out.println(textA);
//get the bytes of encrypted text and assign it to a byte array
byte[] byteofencryptedtext = textA.getBytes();
//activate the decrypt mode of the cipher
aesCipher.init(Cipher.DECRYPT_MODE, secKey);
//decrypt the encrypted text and assign it to a byte array
byte[] byteofencryptedbacktonormaltext = aesCipher.doFinal(byteofencryptedtext);
//change it to string with new string
textB = new String(byteofencryptedbacktonormaltext);
System.out.println(textB);
}
now that encrypt and decrypt are at the same method it works perfectly but I want to change it to a class with different methods so i could encrypt a text with one method and decrypt it with another. but when i separate things decrypt doesn't work well. Encrypt work well. this is the code now:
public class CipherFinalB {
//from https://stackoverflow.com/questions/20796042/aes-encryption-and-decryption-with-java/20796446#20796446
private final byte[] thisismykey;
private final SecretKey secKey;
private final Cipher aesCipher;
public CipherFinalB() throws NoSuchPaddingException, NoSuchAlgorithmException {
thisismykey = "HellodHowfmanyBytesgarehin#htseA".getBytes();
secKey = new SecretKeySpec(thisismykey, "AES");
aesCipher = Cipher.getInstance("AES");
}public String encrypt (String originaltext) throws InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
byte[] myoriginaltexttobyte =originaltext.getBytes();
aesCipher.init(Cipher.ENCRYPT_MODE, secKey);
byte[] bytecipheredoforgtext = aesCipher.doFinal(myoriginaltexttobyte);
String textA = new String(bytecipheredoforgtext);
System.out.println(textA);
return new String(bytecipheredoforgtext);
}
public String decrypt (String encryptedtext) throws InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
byte[] byteofencryptedtext = encryptedtext.getBytes();
aesCipher.init(Cipher.DECRYPT_MODE, secKey);
byte[] byteofencryptedbacktonormaltext = aesCipher.doFinal(byteofencryptedtext);
return new String(byteofencryptedbacktonormaltext);
}
}
when i use the encrypt method it gives me back a string. and when i send the same string to decrypt method it doesn't work and gives me the following error:
Exception in thread "main" javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
at java.base/com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:936)
at java.base/com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:847)
at java.base/com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2191)
at main.CipherFinalB.decrypt(CipherFinalB.java:66)
at main.CipherTest.main(CipherTest.java:16)
What should i do?
UPDATE ANSWER:
as #Juan said the problem was "when data is ciphered you may have any byte in the array, not only printable characters." So I changed the method to return byte for encrypt method and decrypt method. decrypt method now gets byte as parameter instead of string and now everything works fine.
the updated code looks like this:
public byte[] encrypt (String originaltext) throws InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
byte[] myoriginaltexttobyte =originaltext.getBytes();
aesCipher.init(Cipher.ENCRYPT_MODE, secKey);
byte[] bytecipheredoforgtext = aesCipher.doFinal(myoriginaltexttobyte);
String textA = new String(bytecipheredoforgtext);
System.out.println(textA);
return bytecipheredoforgtext;
}
public byte[] decrypt (byte[] encryptedtext) throws InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
byte[] byteofencryptedtext = encryptedtext;
aesCipher.init(Cipher.DECRYPT_MODE, secKey);
byte[] byteofencryptedbacktonormaltext = aesCipher.doFinal(byteofencryptedtext);
return byteofencryptedbacktonormaltext;
}
I am not sure that the error is caused by this but the conversion between String and byte[] you are doing may work with bytes in the character set range, but when data is ciphered you may have any byte in the array, not only printable characters.
After you get the byte[] with the cyphered text, encode it as Base64 and store the string representation.
When decrypting, first decode the Base64 into the byte[] and then decipher it.
See Base64 class

how to compare sha1 encripted password in Java?

Let say i have encripted SHA1 password like this
String pass = "f6ce584e7b4ff5253eed4a2ea2b44247";
and i want make condition like this :
if (pass.equals("userinput")){
System.out.println("success");
}
please someone help me to make proper condition / function to compare those both value between user input and encripted password. Your help will be highly appreciated. thanks
SHA1 is a hash algorithm, which means that it is one-way. You can't get the original message after hashing it. Unlike encryption which is two-way (allows encryption and decryption).
This means that if you want to compare a hash, you don't try to get the original message. Instead, you hash the message-to-be-compared as well, then you perform the match:
So if the hashed pw is stored as:
String pass = "f6ce584e7b4ff5253eed4a2ea2b44247";
To match the subsequent input of the password, you do:
//check if hashed userInput is also "f6ce584e7b4ff5253eed4a2ea2b44247"
if(pass.equals(sha1(userInput))){
//do whatever
}
To implement a sha1() hash function, refer to: Java String to SHA1
To get your hashcode:
public static byte[] sha1(byte[] data)
Calculates the SHA-1 digest and returns the value as a byte[].
Parameters:
data - Data to digest
Returns:
SHA-1 digest
Found these at
https://commons.apache.org/proper/commons-codec/apidocs/org/apache/commons/codec/digest/DigestUtils.html#sha1Hex(java.lang.String)
This helps your process.
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.jcajce.provider.asymmetric.rsa.DigestSignatureSpi.SHA1;
public class SHA1_test {
public static String sha1(String s, String keyString)
throws UnsupportedEncodingException, NoSuchAlgorithmException,
InvalidKeyException {
SecretKeySpec key = new SecretKeySpec((keyString).getBytes("UTF-8"),
"HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(key);
byte[] bytes = mac.doFinal(s.getBytes("UTF-8"));
return new String(Base64.encodeBase64(bytes));
}
public static void main(String[] args) throws InvalidKeyException,
UnsupportedEncodingException, NoSuchAlgorithmException {
Boolean validate = false;
String code = sha1("admin", "123456");
String your_user_inputString = "testpassword";
if (code.equals(sha1(your_user_inputString, "123456"))) {
System.out.println("Correct");
} else {
System.out.println("Bad password");
}
}
}
This works!!!

GNU Crypto Encrypt returns blank string

I'm using GNU Crypto library to encrypt simple strings. I believe I have followed to documentation correctly, but the problem is that it just returns an blank string (in this case 5 characters) of spaces. I'm not sure whether I miss coded it or if its some encoding issue. I hope its not something embarrassingly simple.
import gnu.crypto.cipher.CipherFactory;
import gnu.crypto.cipher.IBlockCipher;
import java.util.HashMap;
import java.util.Map;
public class FTNSAMain {
public static void main(String[] args) throws Exception {
String data = "Apple";
String key = "ABCDEFGHIJKLMNOP";
byte[] temp = Encrypt(data.getBytes(), key.getBytes(), "AES");
System.out.println(new String(temp));
}
public static byte[] Encrypt(byte[] input, byte[] key, String algorithm) throws Exception {
byte[] output = new byte[input.length];
IBlockCipher cipher = CipherFactory.getInstance(algorithm);
Map attributes = new HashMap();
attributes.put(IBlockCipher.CIPHER_BLOCK_SIZE, 16);
attributes.put(IBlockCipher.KEY_MATERIAL, key);
cipher.init(attributes);
int bs = cipher.currentBlockSize();
for (int i = 0; i + bs < input.length; i += bs) {
cipher.encryptBlock(input, i, output, i);
}
return output;
}
}
GNU Crypto documentation have the following to say about the void encryptBlock(..) methode:
Encrypts a block of bytes from plaintext starting at inOffset, storing
the encrypted bytes in ciphertext, starting at outOffset. It is up to
the programmer to ensure that there is at least one full block in
plaintext from inOffset and space for one full block in ciphertext
from outOffset. A java.lang.IllegalStateException will be thrown if
the cipher has not been initialized.
Your input:
String data = "Apple";
Is not a full datablock as AES needs data in blocks of 16 bytes. Also, your output buffer is also too short.
For starters, try encrypting with an input that ends up as 16 bytes like:
String data = "Apple56789abcdef";

Determining that decryption has occurred

I am developing a GUI based encryptor/decryptor based on AES-128 bit symmetric encryption.
My problem is that how to determine that decryption has not occurred and show a dialog box "Decryption Failed". The code I have written would always generate a file without a .enc extension regardless of the fact that it is still encrypted !
Hoping to get a answer as always from Stack Overflow's top notch programmers :)
Do note that the decryption process doesn't fail or throws exception ! It's just the fact that it generates a file that's still not decrypted. That we have to stop and that's what I meant !
Code here: (Sorry for bad indentation !)
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.security.MessageDigest;
public class FileEncryptor{
private String algo;
private String path;
private String password;
public FileEncryptor(String algo,String path, String password) {
this.algo = algo; //setting algo
this.path = path;//setting file path
this.password = password;
}
public void encrypt() throws Exception{
SecureRandom padding = new SecureRandom();
byte[] salt = new byte[16];
padding.nextBytes(salt);
//generating key
byte k[] = password.getBytes();
MessageDigest sha = MessageDigest.getInstance("SHA-1");
k = sha.digest(k);
k = Arrays.copyOf(k, 16);
SecretKeySpec key = new SecretKeySpec(k,algo);
//creating and initialising cipher and cipher streams
Cipher encrypt = Cipher.getInstance(algo);
encrypt.init(Cipher.ENCRYPT_MODE, key);
//opening streams
FileOutputStream fos =new FileOutputStream(path+".enc");
try(FileInputStream fis =new FileInputStream(path)){
try(CipherOutputStream cout=new CipherOutputStream(fos, encrypt)){
copy(fis,cout);
}
}
}
public void decrypt() throws Exception{
SecureRandom padding = new SecureRandom();
byte[] salt = new byte[16];
padding.nextBytes(salt);
//generating same key
byte k[] = password.getBytes();
MessageDigest sha = MessageDigest.getInstance("SHA-1");
k = sha.digest(k);
k = Arrays.copyOf(k, 16);
SecretKeySpec key = new SecretKeySpec(k,algo);
//creating and initialising cipher and cipher streams
Cipher decrypt = Cipher.getInstance(algo);
decrypt.init(Cipher.DECRYPT_MODE, key);
//opening streams
FileInputStream fis = new FileInputStream(path);
try(CipherInputStream cin=new CipherInputStream(fis, decrypt)){
try(FileOutputStream fos =new FileOutputStream(path.substring(0,path.lastIndexOf(".")))){
copy(cin,fos);
}
}
}
private void copy(InputStream is,OutputStream os) throws Exception{
byte buf[] = new byte[4096]; //4K buffer set
int read = 0;
while((read = is.read(buf)) != -1) //reading
os.write(buf,0,read); //writing
}
public static void main (String[] args)throws Exception {
System.out.println("Enter Password: ");
new FileEncryptor("AES","sample.txt",new java.util.Scanner(System.in).nextLine()).encrypt();
new FileEncryptor("AES","sample.txt.enc",new java.util.Scanner(System.in).nextLine()).decrypt();
}
}
Without looking at the API calls, the decrypt methods should throw an exception if an error occurs. In your exception handler, you can set a flag that will allow you to display an error message. You can also delay the decrypted file creation till after successful decryption (or at least till after the first block has been successfully decrypted). If decryption then fails further along the line, you can delete the (essentially temporary) decrypted output file and display the error message.
[edit]
I slightly misunderstood the original post, so some suggestions to check for failed decryption (note that these are higher level than AES, so it might be specific to your application only):
Add a checksum to the plaintext data before encryption
Append other metadata (file size, user, date, etc) to the plaintext, and check for these when decrypting
Usually, a padding exception would occur on decryption - check for these (and any other giveaways)
Use PKI (public key infrastructure) functionality such as signatures (this is outside the scope of this answer, and possibly outside the scope of the problem you're trying to solve)
I suggest appending a constant, rather than a checksum, to your data before encryption, and verifying it after encryption.
And the encryption algorithm should use chaining, that means avoid ECB (see here why: http://bobnalice.wordpress.com/2009/01/28/friends-don%E2%80%99t-let-friends-use-ecb-mode-encryption).
Using a constant with chaining, is nearly as good as a checksum and much simpler.

Categories