更新
该库已经支持 Java/Kotlin,请参阅 github。
源语言
为了简化,我做了一个简单使用的类,我将其添加到加密库中以使用它,您只需执行以下操作:
添加 gradle 库:
compile 'se.simbio.encryption:library:2.0.0'
并使用它:
Encryption encryption = Encryption.getDefault("Key", "Salt", new byte[16]);
String encrypted = encryption.encryptOrNull("top secret string");
String decrypted = encryption.decryptOrNull(encrypted);
如果不想添加加密库,只需将以下类复制到项目中即可。如果你在一个Android项目中,你需要在这个类中导入android Base64,如果你在一个纯java项目中,你需要手动添加这个类,你可以在这里得到它
加密.java
package se.simbio.encryption;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
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.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
/**
* A class to make more easy and simple the encrypt routines, this is the core of Encryption library
*/
public class Encryption {
/**
* The Builder used to create the Encryption instance and that contains the information about
* encryption specifications, this instance need to be private and careful managed
*/
private final Builder mBuilder;
/**
* The private and unique constructor, you should use the Encryption.Builder to build your own
* instance or get the default proving just the sensible information about encryption
*/
private Encryption(Builder builder) {
mBuilder = builder;
}
/**
* @return an default encryption instance or {@code null} if occur some Exception, you can
* create yur own Encryption instance using the Encryption.Builder
*/
public static Encryption getDefault(String key, String salt, byte[] iv) {
try {
return Builder.getDefaultBuilder(key, salt, iv).build();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
/**
* Encrypt a String
*
* @param data the String to be encrypted
*
* @return the encrypted String or {@code null} if you send the data as {@code null}
*
* @throws UnsupportedEncodingException if the Builder charset name is not supported or if
* the Builder charset name is not supported
* @throws NoSuchAlgorithmException if the Builder digest algorithm is not available
* or if this has no installed provider that can
* provide the requested by the Builder secret key
* type or it is {@code null}, empty or in an invalid
* format
* @throws NoSuchPaddingException if no installed provider can provide the padding
* scheme in the Builder digest algorithm
* @throws InvalidAlgorithmParameterException if the specified parameters are inappropriate for
* the cipher
* @throws InvalidKeyException if the specified key can not be used to initialize
* the cipher instance
* @throws InvalidKeySpecException if the specified key specification cannot be used
* to generate a secret key
* @throws BadPaddingException if the padding of the data does not match the
* padding scheme
* @throws IllegalBlockSizeException if the size of the resulting bytes is not a
* multiple of the cipher block size
* @throws NullPointerException if the Builder digest algorithm is {@code null} or
* if the specified Builder secret key type is
* {@code null}
* @throws IllegalStateException if the cipher instance is not initialized for
* encryption or decryption
*/
public String encrypt(String data) throws UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, InvalidKeySpecException, BadPaddingException, IllegalBlockSizeException {
if (data == null) return null;
SecretKey secretKey = getSecretKey(hashTheKey(mBuilder.getKey()));
byte[] dataBytes = data.getBytes(mBuilder.getCharsetName());
Cipher cipher = Cipher.getInstance(mBuilder.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, secretKey, mBuilder.getIvParameterSpec(), mBuilder.getSecureRandom());
return Base64.encodeToString(cipher.doFinal(dataBytes), mBuilder.getBase64Mode());
}
/**
* This is a sugar method that calls encrypt method and catch the exceptions returning
* {@code null} when it occurs and logging the error
*
* @param data the String to be encrypted
*
* @return the encrypted String or {@code null} if you send the data as {@code null}
*/
public String encryptOrNull(String data) {
try {
return encrypt(data);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* This is a sugar method that calls encrypt method in background, it is a good idea to use this
* one instead the default method because encryption can take several time and with this method
* the process occurs in a AsyncTask, other advantage is the Callback with separated methods,
* one for success and other for the exception
*
* @param data the String to be encrypted
* @param callback the Callback to handle the results
*/
public void encryptAsync(final String data, final Callback callback) {
if (callback == null) return;
new Thread(new Runnable() {
@Override
public void run() {
try {
String encrypt = encrypt(data);
if (encrypt == null) {
callback.onError(new Exception("Encrypt return null, it normally occurs when you send a null data"));
}
callback.onSuccess(encrypt);
} catch (Exception e) {
callback.onError(e);
}
}
}).start();
}
/**
* Decrypt a String
*
* @param data the String to be decrypted
*
* @return the decrypted String or {@code null} if you send the data as {@code null}
*
* @throws UnsupportedEncodingException if the Builder charset name is not supported or if
* the Builder charset name is not supported
* @throws NoSuchAlgorithmException if the Builder digest algorithm is not available
* or if this has no installed provider that can
* provide the requested by the Builder secret key
* type or it is {@code null}, empty or in an invalid
* format
* @throws NoSuchPaddingException if no installed provider can provide the padding
* scheme in the Builder digest algorithm
* @throws InvalidAlgorithmParameterException if the specified parameters are inappropriate for
* the cipher
* @throws InvalidKeyException if the specified key can not be used to initialize
* the cipher instance
* @throws InvalidKeySpecException if the specified key specification cannot be used
* to generate a secret key
* @throws BadPaddingException if the padding of the data does not match the
* padding scheme
* @throws IllegalBlockSizeException if the size of the resulting bytes is not a
* multiple of the cipher block size
* @throws NullPointerException if the Builder digest algorithm is {@code null} or
* if the specified Builder secret key type is
* {@code null}
* @throws IllegalStateException if the cipher instance is not initialized for
* encryption or decryption
*/
public String decrypt(String data) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
if (data == null) return null;
byte[] dataBytes = Base64.decode(data, mBuilder.getBase64Mode());
SecretKey secretKey = getSecretKey(hashTheKey(mBuilder.getKey()));
Cipher cipher = Cipher.getInstance(mBuilder.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, secretKey, mBuilder.getIvParameterSpec(), mBuilder.getSecureRandom());
byte[] dataBytesDecrypted = (cipher.doFinal(dataBytes));
return new String(dataBytesDecrypted);
}
/**
* This is a sugar method that calls decrypt method and catch the exceptions returning
* {@code null} when it occurs and logging the error
*
* @param data the String to be decrypted
*
* @return the decrypted String or {@code null} if you send the data as {@code null}
*/
public String decryptOrNull(String data) {
try {
return decrypt(data);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* This is a sugar method that calls decrypt method in background, it is a good idea to use this
* one instead the default method because decryption can take several time and with this method
* the process occurs in a AsyncTask, other advantage is the Callback with separated methods,
* one for success and other for the exception
*
* @param data the String to be decrypted
* @param callback the Callback to handle the results
*/
public void decryptAsync(final String data, final Callback callback) {
if (callback == null) return;
new Thread(new Runnable() {
@Override
public void run() {
try {
String decrypt = decrypt(data);
if (decrypt == null) {
callback.onError(new Exception("Decrypt return null, it normally occurs when you send a null data"));
}
callback.onSuccess(decrypt);
} catch (Exception e) {
callback.onError(e);
}
}
}).start();
}
/**
* creates a 128bit salted aes key
*
* @param key encoded input key
*
* @return aes 128 bit salted key
*
* @throws NoSuchAlgorithmException if no installed provider that can provide the requested
* by the Builder secret key type
* @throws UnsupportedEncodingException if the Builder charset name is not supported
* @throws InvalidKeySpecException if the specified key specification cannot be used to
* generate a secret key
* @throws NullPointerException if the specified Builder secret key type is {@code null}
*/
private SecretKey getSecretKey(char[] key) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeySpecException {
SecretKeyFactory factory = SecretKeyFactory.getInstance(mBuilder.getSecretKeyType());
KeySpec spec = new PBEKeySpec(key, mBuilder.getSalt().getBytes(mBuilder.getCharsetName()), mBuilder.getIterationCount(), mBuilder.getKeyLength());
SecretKey tmp = factory.generateSecret(spec);
return new SecretKeySpec(tmp.getEncoded(), mBuilder.getKeyAlgorithm());
}
/**
* takes in a simple string and performs an sha1 hash
* that is 128 bits long...we then base64 encode it
* and return the char array
*
* @param key simple inputted string
*
* @return sha1 base64 encoded representation
*
* @throws UnsupportedEncodingException if the Builder charset name is not supported
* @throws NoSuchAlgorithmException if the Builder digest algorithm is not available
* @throws NullPointerException if the Builder digest algorithm is {@code null}
*/
private char[] hashTheKey(String key) throws UnsupportedEncodingException, NoSuchAlgorithmException {
MessageDigest messageDigest = MessageDigest.getInstance(mBuilder.getDigestAlgorithm());
messageDigest.update(key.getBytes(mBuilder.getCharsetName()));
return Base64.encodeToString(messageDigest.digest(), Base64.NO_PADDING).toCharArray();
}
/**
* When you encrypt or decrypt in callback mode you get noticed of result using this interface
*/
public interface Callback {
/**
* Called when encrypt or decrypt job ends and the process was a success
*
* @param result the encrypted or decrypted String
*/
void onSuccess(String result);
/**
* Called when encrypt or decrypt job ends and has occurred an error in the process
*
* @param exception the Exception related to the error
*/
void onError(Exception exception);
}
/**
* This class is used to create an Encryption instance, you should provide ALL data or start
* with the Default Builder provided by the getDefaultBuilder method
*/
public static class Builder {
private byte[] mIv;
private int mKeyLength;
private int mBase64Mode;
private int mIterationCount;
private String mSalt;
private String mKey;
private String mAlgorithm;
private String mKeyAlgorithm;
private String mCharsetName;
private String mSecretKeyType;
private String mDigestAlgorithm;
private String mSecureRandomAlgorithm;
private SecureRandom mSecureRandom;
private IvParameterSpec mIvParameterSpec;
/**
* @return an default builder with the follow defaults:
* the default char set is UTF-8
* the default base mode is Base64
* the Secret Key Type is the PBKDF2WithHmacSHA1
* the default salt is "some_salt" but can be anything
* the default length of key is 128
* the default iteration count is 65536
* the default algorithm is AES in CBC mode and PKCS 5 Padding
* the default secure random algorithm is SHA1PRNG
* the default message digest algorithm SHA1
*/
public static Builder getDefaultBuilder(String key, String salt, byte[] iv) {
return new Builder()
.setIv(iv)
.setKey(key)
.setSalt(salt)
.setKeyLength(128)
.setKeyAlgorithm("AES")
.setCharsetName("UTF8")
.setIterationCount(1)
.setDigestAlgorithm("SHA1")
.setBase64Mode(Base64.DEFAULT)
.setAlgorithm("AES/CBC/PKCS5Padding")
.setSecureRandomAlgorithm("SHA1PRNG")
.setSecretKeyType("PBKDF2WithHmacSHA1");
}
/**
* Build the Encryption with the provided information
*
* @return a new Encryption instance with provided information
*
* @throws NoSuchAlgorithmException if the specified SecureRandomAlgorithm is not available
* @throws NullPointerException if the SecureRandomAlgorithm is {@code null} or if the
* IV byte array is null
*/
public Encryption build() throws NoSuchAlgorithmException {
setSecureRandom(SecureRandom.getInstance(getSecureRandomAlgorithm()));
setIvParameterSpec(new IvParameterSpec(getIv()));
return new Encryption(this);
}
/**
* @return the charset name
*/
private String getCharsetName() {
return mCharsetName;
}
/**
* @param charsetName the new charset name
*
* @return this instance to follow the Builder patter
*/
public Builder setCharsetName(String charsetName) {
mCharsetName = charsetName;
return this;
}
/**
* @return the algorithm
*/
private String getAlgorithm() {
return mAlgorithm;
}
/**
* @param algorithm the algorithm to be used
*
* @return this instance to follow the Builder patter
*/
public Builder setAlgorithm(String algorithm) {
mAlgorithm = algorithm;
return this;
}
/**
* @return the key algorithm
*/
private String getKeyAlgorithm() {
return mKeyAlgorithm;
}
/**
* @param keyAlgorithm the keyAlgorithm to be used in keys
*
* @return this instance to follow the Builder patter
*/
public Builder setKeyAlgorithm(String keyAlgorithm) {
mKeyAlgorithm = keyAlgorithm;
return this;
}
/**
* @return the Base 64 mode
*/
private int getBase64Mode() {
return mBase64Mode;
}
/**
* @param base64Mode set the base 64 mode
*
* @return this instance to follow the Builder patter
*/
public Builder setBase64Mode(int base64Mode) {
mBase64Mode = base64Mode;
return this;
}
/**
* @return the type of aes key that will be created, on KITKAT+ the API has changed, if you
* are getting problems please @see <a href="http://android-developers.blogspot.com.br/2013/12/changes-to-secretkeyfactory-api-in.html">http://android-developers.blogspot.com.br/2013/12/changes-to-secretkeyfactory-api-in.html</a>
*/
private String getSecretKeyType() {
return mSecretKeyType;
}
/**
* @param secretKeyType the type of AES key that will be created, on KITKAT+ the API has
* changed, if you are getting problems please @see <a href="http://android-developers.blogspot.com.br/2013/12/changes-to-secretkeyfactory-api-in.html">http://android-developers.blogspot.com.br/2013/12/changes-to-secretkeyfactory-api-in.html</a>
*
* @return this instance to follow the Builder patter
*/
public Builder setSecretKeyType(String secretKeyType) {
mSecretKeyType = secretKeyType;
return this;
}
/**
* @return the value used for salting
*/
private String getSalt() {
return mSalt;
}
/**
* @param salt the value used for salting
*
* @return this instance to follow the Builder patter
*/
public Builder setSalt(String salt) {
mSalt = salt;
return this;
}
/**
* @return the key
*/
private String getKey() {
return mKey;
}
/**
* @param key the key.
*
* @return this instance to follow the Builder patter
*/
public Builder setKey(String key) {
mKey = key;
return this;
}
/**
* @return the length of key
*/
private int getKeyLength() {
return mKeyLength;
}
/**
* @param keyLength the length of key
*
* @return this instance to follow the Builder patter
*/
public Builder setKeyLength(int keyLength) {
mKeyLength = keyLength;
return this;
}
/**
* @return the number of times the password is hashed
*/
private int getIterationCount() {
return mIterationCount;
}
/**
* @param iterationCount the number of times the password is hashed
*
* @return this instance to follow the Builder patter
*/
public Builder setIterationCount(int iterationCount) {
mIterationCount = iterationCount;
return this;
}
/**
* @return the algorithm used to generate the secure random
*/
private String getSecureRandomAlgorithm() {
return mSecureRandomAlgorithm;
}
/**
* @param secureRandomAlgorithm the algorithm to generate the secure random
*
* @return this instance to follow the Builder patter
*/
public Builder setSecureRandomAlgorithm(String secureRandomAlgorithm) {
mSecureRandomAlgorithm = secureRandomAlgorithm;
return this;
}
/**
* @return the IvParameterSpec bytes array
*/
private byte[] getIv() {
return mIv;
}
/**
* @param iv the byte array to create a new IvParameterSpec
*
* @return this instance to follow the Builder patter
*/
public Builder setIv(byte[] iv) {
mIv = iv;
return this;
}
/**
* @return the SecureRandom
*/
private SecureRandom getSecureRandom() {
return mSecureRandom;
}
/**
* @param secureRandom the Secure Random
*
* @return this instance to follow the Builder patter
*/
public Builder setSecureRandom(SecureRandom secureRandom) {
mSecureRandom = secureRandom;
return this;
}
/**
* @return the IvParameterSpec
*/
private IvParameterSpec getIvParameterSpec() {
return mIvParameterSpec;
}
/**
* @param ivParameterSpec the IvParameterSpec
*
* @return this instance to follow the Builder patter
*/
public Builder setIvParameterSpec(IvParameterSpec ivParameterSpec) {
mIvParameterSpec = ivParameterSpec;
return this;
}
/**
* @return the message digest algorithm
*/
private String getDigestAlgorithm() {
return mDigestAlgorithm;
}
/**
* @param digestAlgorithm the algorithm to be used to get message digest instance
*
* @return this instance to follow the Builder patter
*/
public Builder setDigestAlgorithm(String digestAlgorithm) {
mDigestAlgorithm = digestAlgorithm;
return this;
}
}
}