初始化向量和盐与加密数据一起存储,因此您只需要提供密码。我需要它来分发我的私有代码签名密钥,以便我可以从我正在使用的任何计算机上访问解密和签署任何编辑。目标是迁移到 AES-256,但遇到了标题中所述的问题。这是(固定(代码:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.io.IOUtils;
/**
*
* @author Tim
*/
public class CryptoTool {
public static final String className = "CryptoTool";
public static boolean debug = false;
public CryptoTool() {
}
public byte[] encryptFile(String filename,char[] pass, byte[] salt, byte[] iv) {
// generate key
SecretKeySpec secretKeySpec = generateKeyFromPassword(pass,salt);
// erase local password, don't worry, it's fast enough, (local to the function)
for(int i = 0; i < pass.length;i++) pass[i] = ' '; // NULL character (end of string for C/C++)
byte[] data = getBytesUTF8(filename);
byte[] output = null;
if (!new File(filename).exists()) {
System.out.println(className + "encryptFile: non-existent file: " + filename);
// if you don't have a file to operate on, I mean, C'mon, get it together
return null;
}
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, iv);
byte[] aadData = "symService".getBytes();
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, gcmParameterSpec, new SecureRandom());
cipher.updateAAD(aadData);
output = cipher.doFinal(data);
byte[] encrypted = new byte[output.length + 20];
System.arraycopy(salt , 0, encrypted, 0 , 8);
System.arraycopy(iv , 0, encrypted, 8 , 12);
System.arraycopy(output, 0, encrypted, 20,output.length);
writeBytesTo(encrypted,filename + ".encrypted");
//System.out.println("Encrypted To: " + filename + ".encrypted");
new File(filename).delete();
} catch (NoSuchAlgorithmException nsae) {
System.out.println("No Such Algorithm: " + nsae);
} catch (InvalidKeyException ike) {
System.out.println("Invalid Key: " + ike);
} catch (InvalidAlgorithmParameterException iape) {
System.out.println("InvalidAlgorithmParameterException: " + iape);
} catch (NoSuchPaddingException nspe) {
System.out.println("NoSuchPaddingException: " + nspe);
} catch (IllegalBlockSizeException ibse) {
System.out.println("IllegalBlockSizeException: " + ibse);
} catch (BadPaddingException bpe) {
System.out.println("BadPaddingException: " + bpe);
}
return iv;
}
public boolean decryptFile(char[] pass,String outPath) {
// must pass a valid file, note that outPath should end in ".encrypted"
if (outPath == null || !new File(outPath + ".encrypted").exists()) return false;
// get the contents of the file
byte[] input = getBytesUTF8(outPath + ".encrypted");
// from the contents, get the salt and generate the key, so we can erase the password
byte[] salt = new byte[8];
System.arraycopy(input,0,salt,0,8);
if (debug) {
for(int i = 0; i < salt.length;i++) System.out.print((char)salt[i]);
System.out.println();
}
// generate the key
SecretKeySpec secretKeySpec = generateKeyFromPassword(pass,salt);
for(int i = 0; i < pass.length;i++) pass[i] = ' ';
// get the initalization vector
byte[] iv = new byte[12];
System.arraycopy(input, 8, iv, 0, 12);
if (debug) {
for(int i = 0; i < salt.length;i++) System.out.print((char)salt[i]);
System.out.println();
}
// copy the encrypted data to a byte array
byte[] encrypted = new byte[input.length - 20];
System.arraycopy(input, 20, encrypted, 0, input.length - 20);
// get the authenticated additional data (the tag we're having trouble with)
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, iv);
byte[] aadData = "symService".getBytes();
// decrypt
byte[] decrypted = null;
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, gcmParameterSpec, new SecureRandom());
cipher.updateAAD(aadData);
decrypted = cipher.doFinal(encrypted);
// save the decrypted file to its' original filename (removes the .encrypted from the filename)
writeBytesTo(decrypted,outPath);
// delete the encrypted file
new File(outPath + ".encrypted").delete();
// if we made it this far, nothing failed, so return true; decryption successful
return true;
} catch (NoSuchAlgorithmException nsae) {
System.out.println("No Such Algorithm: " + nsae);
} catch (InvalidKeyException ike) {
System.out.println("Invalid Key: " + ike);
} catch (InvalidAlgorithmParameterException iape) {
System.out.println("InvalidAlgorithmParameterException: " + iape);
} catch (NoSuchPaddingException nspe) {
System.out.println("NoSuchPaddingException: " + nspe);
} catch (IllegalBlockSizeException ibse) {
System.out.println("IllegalBlockSizeException: " + ibse);
} catch (BadPaddingException bpe) {
System.out.println("BadPaddingException: " + bpe);
bpe.printStackTrace();
}
// otherwise, something went wrong, return failure code
return false;
}
public static void main(String[] args) {
/* The purpose of this test is to encrypt and decrypt a keystore file so we
may distribute our keys, without fear of someone hacking the data and
signing code we don't authorize.
If it works, we can distribute our keys and have immediate access to them
without fear that anyone else can gain access to them. This allows us to
deploy from any machine attached to the Internet.
*/
// toggle 'test' in order to test both encryption and decryption, or separately.
boolean test = true;
CryptoTool tool = new CryptoTool();
char[] pass = new RequestPassword().getPassword();
SecureRandom sr = new SecureRandom();
byte[] salt = new byte[8];
sr.nextBytes(salt);
byte[] iv = new byte[12];
// maybe we could skip this next line, so delete that and just use one instance of SecureRandom???
sr = new SecureRandom();
sr.nextBytes(iv);
if (debug) {
for(int i = 0; i < salt.length;i++) System.out.print((char)salt[i]);
System.out.println();
for(int i = 0; i < iv.length;i++) System.out.print((char)iv[i]);
System.out.println();
}
String plainFile = "c:\java\keystore_private";
// if test is true, encrypt also, otherwise, just decrypt (in this case,
// salt and IV should be generated, else, get them from the encrypted file.
if (test) {
tool.encryptFile(plainFile, pass, salt, iv);
}
if (new File(plainFile + ".encrypted").exists())
System.out.println("Encrypted To: " + plainFile + ".encrypted");
pass = new RequestPassword().getPassword();
if (tool.decryptFile(pass,plainFile)) {
System.out.println("Decrypted To: " + plainFile);
} else {
System.out.println("Couldn't Decrypt");
}
// does erasing in a function(method) also erase original? Local to method call.
if (debug) {
System.out.print("Pass: '");
for(int i = 0; i < pass.length;i++) System.out.print(pass[i]);
System.out.println("'");
}
System.exit(0);
}
public byte[] getBytesUTF8(String filename) {
FileInputStream in = null;
try {
in = new FileInputStream(filename);
} catch (FileNotFoundException fnfe) {
// So using this package, that shouldn't happend, but anyway
System.out.println(className + ".getBytesUTF8: File not found: " + filename);
}
// okay, so load the class
byte[] data = null;
try {
data = IOUtils.toByteArray(in);
} catch (IOException ioe) {
System.err.println("IOException: reading class data.");
}
try {
in.close();
} catch (IOException ioe) {
System.out.println("Unable to close: " + filename);
}
return data;
}
public void writeBytesTo(byte[] data, String filename) {
FileOutputStream output = null;
try {
output = new FileOutputStream(new File(filename));
} catch (FileNotFoundException fnfe) {
System.err.println("Couldn't create: " + filename);
}
try {
IOUtils.write(data, output);
//System.out.println("Wrote file: " + filename);
} catch (IOException ioe) {
System.err.println("Couldn't write data to: " + filename);
}
try {
output.close();
} catch (IOException ioe) {
System.out.println("Couldn't close: " + filename);
}
}
public SecretKeySpec generateKeyFromPassword(char[] password, byte[] salt) {
/* Derive the key, given password and salt. */
SecretKeyFactory factory = null;
try {
factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
} catch (NoSuchAlgorithmException nsae) {
System.out.println("No Such Algorithm: " + nsae);
}
KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
SecretKey tmp = null;
try {
tmp = factory.generateSecret(spec);
} catch (InvalidKeySpecException ikse) {
System.out.println("Inavalid Key Spec Exception: " + ikse);
}
SecretKeySpec keySpec = new SecretKeySpec(tmp.getEncoded(), "AES");
return keySpec;
}
}
我想通了。代码正在擦除密码,因此在解密阶段,传递将等于",这不应该是正确的。因此,aeadException是指pass或iv不正确。