\$\begingroup\$
\$\endgroup\$
1
I made a simple file-encryption utility application for a little school project. I'm really interested in cryptography, and tried my best while making it. Can you see it, and give me feedback?
The input is always a FileInputStream
(wrapped by a BufferedInputStream
), and the output is a ByteBufferOutputStream
, which will be written to the output file.
public class Cryptography {
private final Cipher c;
private final MessageDigest md5;
private final String AES_CBC_PKCS5 = "AES/CBC/PKCS5Padding";
private final String MD5 = "MD5";
private final int BLOCK_SIZE = 16;
private final byte[] bytesIV = new byte[BLOCK_SIZE];
public Cryptography() throws NoSuchAlgorithmException, NoSuchPaddingException {
c = Cipher.getInstance(AES_CBC_PKCS5);
md5 = MessageDigest.getInstance(MD5);
}
public void initIV() {
new SecureRandom().nextBytes(bytesIV);
}
public void encryptStream(InputStream in, OutputStream out, byte[] key) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, IOException {
out.write(bytesIV);
out.flush();
c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(bytesIV));
out = new CipherOutputStream(out, c);
byte[] buf = new byte[1024];
int numRead = 0;
while ((numRead = in.read(buf)) >= 0) {
out.write(buf, 0, numRead);
}
out.close();
}
public void decryptStream(InputStream in, OutputStream out, byte[] key) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, IOException {
in.read(bytesIV);
c.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(bytesIV));
in = new CipherInputStream(in, c);
byte[] buf = new byte[1024];
int numRead = 0;
while ((numRead = in.read(buf)) >= 0) {
out.write(buf, 0, numRead);
}
out.close();
}
public byte[] hash(byte[] key) {
return md5.digest(key);
}
public byte[] getIV() {
byte[] temp = new byte[BLOCK_SIZE];
System.arraycopy(bytesIV, 0, temp, 0, BLOCK_SIZE);
return temp;
}
}
-
1\$\begingroup\$ I have rolled back the last edit. Please see What to do when someone answers . \$\endgroup\$Mast– Mast ♦2016年05月17日 16:26:33 +00:00Commented May 17, 2016 at 16:26
2 Answers 2
\$\begingroup\$
\$\endgroup\$
2
- Exposing the IV only leaves room for errors. Simply generate it at the start of encryption.
- There is no point in having a completely unrelated
hash
method on this class. - There is not integrity protection, so an attacker who can modify the ciphertext you decrypt will likely be able to pull off a padding-oracle attack. Unfortunately it's not trivial to combine streaming decryption with integrity protection, since you must ensure that you don't release any plaintext you haven't authenticated.
- Once you eliminate the IV storage, as recommended above, you have no state to preserve across calls, which means you don't need any instance fields anymore. They only make your class thread-unsafe without any benefit.
- Closing
out
seems pretty weird, I wouldn't expect that behaviour. On the other hand you don't closein
. - If you decide to dispose resources, use
try...finally
(or try-with-resources) to do so reliably. - I'd make the class immutable and threadsafe, storing only the key (and algorithm if it's not hardcoded) in it.
answered Jun 10, 2016 at 7:32
-
\$\begingroup\$ Thank you for your answer but meanwhile I re-created my Cryptography class. You can see it here: github.com/fulopm/filetitok/blob/master/src/filetitok/crypto/… \$\endgroup\$mrkflp– mrkflp2016年06月18日 19:08:43 +00:00Commented Jun 18, 2016 at 19:08
-
\$\begingroup\$ @mrkflp Most of my points still apply to your updated version. \$\endgroup\$CodesInChaos– CodesInChaos2016年06月18日 19:38:13 +00:00Commented Jun 18, 2016 at 19:38
\$\begingroup\$
\$\endgroup\$
9
Some general comments:
- String initialisaion, avoid things like
String AES_CBC_PKCS5 = "AES/CBC/PKCS5Padding";
- wrap the "..." innew String( "..." );
Same comment forMD5
. - Variable naming, use lower case as a preference, camelCase with variable names that are multiple words (like you have with method names). Avoid underscores separating words in variable names.
- If you're initialising some things in the Constructor, you may as well initialise them in the variable declaration (variables are generated immediately prior to Constructor calls).
- Your private fields get no benefit from the
final
keyword, and indeed, you shouldn't have afinal
IV. Block size probably should be passed into the Constructor (and sanitized). But then, you could also pass the cipher spec at runtime too. - There's no point generating a new
SecureRandom
every time you want toinitIV()
- move SecureRandom to a private field. - You're propogating an awful lot of exceptions further up the stack. Avoid this. Handle the exceptions in an acceptable way, re-throw exceptions (even custom ones) where necessary.
encryptStream()
doesn't only encrypt. It writes to another stream. Do you need to work with streams here? I'd suggest working with an array of bytes is more appropriate (and allows you to change keys between blocks of bytes at arbitary lengths). Same comment fordecryptStream()
- If you really need to read from stream, encrypt and immediately write to another stream, rename the methods to be descriptive. Also, since you always close
out
, maybe you should closein
too? Or maybe you should let the caller decide (by allowing them to specify a boolean argument; or by not doing anything and providing JavaDoc comment stating why you're not closing resources). hash()
is a poorly named method. Consider something likegetKeySum()
getIV()
is a poorly named method, and in it's current implementation is only appropriate if you've encrypted the stream and haven't calledinitIV()
again.decryptStream()
assumes that the IV is the same as the one generated ininitIV()
- this gives rise to a fairly trivial bug if I give you a stream that was encrypted with a previously generated IV.
-
\$\begingroup\$ I'm really grateful to you for your suggestions. I'll implement all of them soon. About the exception handling: I forward all of the exceptions to the GUI, then handle them (log + inform the user about what's happening, in a graphical way). A custom exception (or more), instead of throwing a lot of various is a good idea too, I'll think it over. Thank you again! \$\endgroup\$mrkflp– mrkflp2016年05月09日 21:24:04 +00:00Commented May 9, 2016 at 21:24
-
4\$\begingroup\$ The suggestion of using
new String("MD5")
instead of a simple"MD5"
is wrong. Just using the latter is great code. \$\endgroup\$Roland Illig– Roland Illig2016年05月09日 21:48:16 +00:00Commented May 9, 2016 at 21:48 -
\$\begingroup\$ Wrong for you maybe, but that's the point of Code Review. In work, my code wouldn't pass a Code Review using the "simple" version. You're explicitly choosing to create a new object. If the fast-initialisation method is required then comments should be used expressing why you don't believe a new object is required. Also, the hash alg. should really be a parameter, as md5 has known hash collision problems. \$\endgroup\$Dave– Dave2016年05月09日 22:16:16 +00:00Commented May 9, 2016 at 22:16
-
6\$\begingroup\$ Your work-place should really re-consider their coding standards. What benefit do you gain from
new
ing up eachString
explicitly except for actively working against an important JVM optimization and making the code more awkward to read and type? It is also contrary to every other Java coding standard I have ever seen. Robust Java code should never rely on the identity of aString
object so neither way of "creating" aString
should be "required" for the proper functioning of the code. \$\endgroup\$5gon12eder– 5gon12eder2016年05月09日 23:35:34 +00:00Commented May 9, 2016 at 23:35 -
2\$\begingroup\$ @Dave that's a stupid justification to require the JVM to do needless work and waste memory.... Aside from that, I find your recommendation against using ` final` actively harmful. The suggestion about not reinitializing
SecureRandom
is actually a security flaw and simply wrong. On the other hand the advice about not using Streams is great and your naming recommendations are nice... I'm currently rather torn, but overall your answer has more good than bad. Just the bad is really bad. Feel free to drop by in Code Review Chat and drop me a note to discuss this further. \$\endgroup\$Vogel612– Vogel6122016年05月17日 16:32:15 +00:00Commented May 17, 2016 at 16:32
lang-java