View Javadoc
1   package org.argeo.ident;
2   
3   import java.nio.charset.StandardCharsets;
4   import java.security.GeneralSecurityException;
5   import java.security.MessageDigest;
6   import java.util.Arrays;
7   import java.util.Base64;
8   
9   import javax.crypto.BadPaddingException;
10  import javax.crypto.Cipher;
11  import javax.crypto.IllegalBlockSizeException;
12  import javax.crypto.spec.IvParameterSpec;
13  import javax.crypto.spec.SecretKeySpec;
14  
15  /**
16   * Decrypts OpenSSL encrypted data.
17   * 
18   * From
19   * https://stackoverflow.com/questions/11783062/how-to-decrypt-file-in-java-encrypted-with-openssl-command-using-aes
20   * 
21   * See also
22   * https://stackoverflow.com/questions/54171959/badpadding-exception-when-trying-to-decrypt-aes-based-encrypted-text/54173509#54173509
23   * for new default message digest (not yet in CentOS 7 as of July 2019)
24   */
25  public class OpenSslDecryptor {
26  	private static final int INDEX_KEY = 0;
27  	private static final int INDEX_IV = 1;
28  	private static final int ITERATIONS = 1;
29  
30  	private static final int SALT_OFFSET = 8;
31  	private static final int SALT_SIZE = 8;
32  	private static final int CIPHERTEXT_OFFSET = SALT_OFFSET + SALT_SIZE;
33  
34  	/** In bits. */
35  	private final int keySize;
36  
37  	private Cipher cipher;
38  	private MessageDigest messageDigest;
39  
40  	public OpenSslDecryptor() {
41  		/*
42  		 * Changed to SHA-256 from OpenSSL v1.1.0 (see
43  		 * https://stackoverflow.com/questions/39637388/encryption-decryption-doesnt-
44  		 * work-well-between-two-different-openssl-versions)
45  		 */
46  		this(128, "MD5");
47  	}
48  
49  	public OpenSslDecryptor(int keySize, String messageDigest) {
50  		this.keySize = keySize;
51  		try {
52  			this.cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
53  			this.messageDigest = MessageDigest.getInstance(messageDigest);
54  		} catch (GeneralSecurityException e) {
55  			throw new IllegalArgumentException("Cannot initialise decryptor", e);
56  		}
57  	}
58  
59  	public String decryptAuthd(String dataBase64, String passphrase) {
60  		try {
61  			byte[] headerSaltAndCipherText = Base64.getDecoder().decode(dataBase64);
62  
63  			boolean withSalt = true;
64  			byte[] salt = withSalt ? Arrays.copyOfRange(headerSaltAndCipherText, SALT_OFFSET, SALT_OFFSET + SALT_SIZE)
65  					: null;
66  			byte[] encrypted = withSalt
67  					? Arrays.copyOfRange(headerSaltAndCipherText, CIPHERTEXT_OFFSET, headerSaltAndCipherText.length)
68  					: headerSaltAndCipherText;
69  
70  			final byte[][] keyAndIV = EVP_BytesToKey(keySize / Byte.SIZE, cipher.getBlockSize(), messageDigest, salt,
71  					passphrase.getBytes(StandardCharsets.US_ASCII), ITERATIONS);
72  			SecretKeySpec key = new SecretKeySpec(keyAndIV[INDEX_KEY], "AES");
73  			IvParameterSpec iv = new IvParameterSpec(keyAndIV[INDEX_IV]);
74  
75  			cipher.init(Cipher.DECRYPT_MODE, key, iv);
76  			byte[] decrypted = cipher.doFinal(encrypted);
77  
78  			String answer = new String(decrypted, StandardCharsets.US_ASCII);
79  			return answer;
80  		} catch (BadPaddingException e) {
81  			throw new IllegalStateException("Bad password, algorithm, mode or padding;"
82  					+ " no salt, wrong number of iterations or corrupted ciphertext.", e);
83  		} catch (IllegalBlockSizeException e) {
84  			throw new IllegalStateException("Bad algorithm, mode or corrupted (resized) ciphertext.", e);
85  		} catch (GeneralSecurityException e) {
86  			throw new IllegalStateException(e);
87  		}
88  	}
89  
90  	private static byte[][] EVP_BytesToKey(int key_len, int iv_len, MessageDigest md, byte[] salt, byte[] data,
91  			int count) {
92  		byte[][] both = new byte[2][];
93  		byte[] key = new byte[key_len];
94  		int key_ix = 0;
95  		byte[] iv = new byte[iv_len];
96  		int iv_ix = 0;
97  		both[0] = key;
98  		both[1] = iv;
99  		byte[] md_buf = null;
100 		int nkey = key_len;
101 		int niv = iv_len;
102 		int i = 0;
103 		if (data == null) {
104 			return both;
105 		}
106 		int addmd = 0;
107 		for (;;) {
108 			md.reset();
109 			if (addmd++ > 0) {
110 				md.update(md_buf);
111 			}
112 			md.update(data);
113 			if (null != salt) {
114 				md.update(salt, 0, 8);
115 			}
116 			md_buf = md.digest();
117 			for (i = 1; i < count; i++) {
118 				md.reset();
119 				md.update(md_buf);
120 				md_buf = md.digest();
121 			}
122 			i = 0;
123 			if (nkey > 0) {
124 				for (;;) {
125 					if (nkey == 0)
126 						break;
127 					if (i == md_buf.length)
128 						break;
129 					key[key_ix++] = md_buf[i];
130 					nkey--;
131 					i++;
132 				}
133 			}
134 			if (niv > 0 && i != md_buf.length) {
135 				for (;;) {
136 					if (niv == 0)
137 						break;
138 					if (i == md_buf.length)
139 						break;
140 					iv[iv_ix++] = md_buf[i];
141 					niv--;
142 					i++;
143 				}
144 			}
145 			if (nkey == 0 && niv == 0) {
146 				break;
147 			}
148 		}
149 		for (i = 0; i < md_buf.length; i++) {
150 			md_buf[i] = 0;
151 		}
152 		return both;
153 	}
154 
155 	public static void main(String[] args) {
156 		String dataBase64 = args[0];
157 		String passphrase = args[1];
158 		OpenSslDecryptor decryptor = new OpenSslDecryptor();
159 		System.out.println(decryptor.decryptAuthd(dataBase64, passphrase));
160 	}
161 
162 }