|
|
/*
|
|
|
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
|
|
|
*
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
* You may obtain a copy of the License at
|
|
|
*
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
*
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
* See the License for the specific language governing permissions and
|
|
|
* limitations under the License.
|
|
|
*/
|
|
|
|
|
|
package net.micode.notes.tool;
|
|
|
|
|
|
import android.util.Base64;
|
|
|
import android.util.Log;
|
|
|
|
|
|
import java.io.UnsupportedEncodingException;
|
|
|
import java.security.GeneralSecurityException;
|
|
|
import java.security.MessageDigest;
|
|
|
import java.security.NoSuchAlgorithmException;
|
|
|
import java.security.SecureRandom;
|
|
|
import java.util.Arrays;
|
|
|
|
|
|
import javax.crypto.Cipher;
|
|
|
import javax.crypto.KeyGenerator;
|
|
|
import javax.crypto.SecretKey;
|
|
|
import javax.crypto.SecretKeyFactory;
|
|
|
import javax.crypto.spec.IvParameterSpec;
|
|
|
import javax.crypto.spec.PBEKeySpec;
|
|
|
import javax.crypto.spec.SecretKeySpec;
|
|
|
|
|
|
/**
|
|
|
* 加密工具类,提供AES加密/解密、密码哈希生成和验证、密保问题答案哈希生成和验证等功能
|
|
|
*/
|
|
|
public class EncryptionUtils {
|
|
|
private static final String TAG = "EncryptionUtils";
|
|
|
|
|
|
// ====================== 常量定义 ======================
|
|
|
|
|
|
/**
|
|
|
* 加密算法:AES/CBC/PKCS7Padding
|
|
|
*/
|
|
|
public static final String ENCRYPTION_ALGORITHM = "AES/CBC/PKCS7Padding";
|
|
|
|
|
|
/**
|
|
|
* 密钥生成算法:PBKDF2WithHmacSHA256
|
|
|
*/
|
|
|
private static final String KEY_GENERATION_ALGORITHM = "PBKDF2WithHmacSHA256";
|
|
|
|
|
|
/**
|
|
|
* 哈希算法:SHA-256
|
|
|
*/
|
|
|
private static final String HASH_ALGORITHM = "SHA-256";
|
|
|
|
|
|
/**
|
|
|
* 密钥长度:256位
|
|
|
*/
|
|
|
private static final int KEY_LENGTH = 256;
|
|
|
|
|
|
/**
|
|
|
* 盐值长度:16字节
|
|
|
*/
|
|
|
private static final int SALT_LENGTH = 16;
|
|
|
|
|
|
/**
|
|
|
* 初始化向量长度:16字节
|
|
|
*/
|
|
|
private static final int IV_LENGTH = 16;
|
|
|
|
|
|
/**
|
|
|
* 迭代次数:10000
|
|
|
*/
|
|
|
private static final int ITERATION_COUNT = 10000;
|
|
|
|
|
|
// ====================== 工具方法 ======================
|
|
|
|
|
|
/**
|
|
|
* 生成随机盐值
|
|
|
* @return 随机盐值的Base64编码字符串
|
|
|
*/
|
|
|
public static String generateSalt() {
|
|
|
SecureRandom random = new SecureRandom();
|
|
|
byte[] salt = new byte[SALT_LENGTH];
|
|
|
random.nextBytes(salt);
|
|
|
return Base64.encodeToString(salt, Base64.NO_WRAP);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 生成随机初始化向量
|
|
|
* @return 随机初始化向量的Base64编码字符串
|
|
|
*/
|
|
|
public static String generateIv() {
|
|
|
SecureRandom random = new SecureRandom();
|
|
|
byte[] iv = new byte[IV_LENGTH];
|
|
|
random.nextBytes(iv);
|
|
|
return Base64.encodeToString(iv, Base64.NO_WRAP);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 基于密码和盐值生成密钥
|
|
|
* @param password 密码
|
|
|
* @param salt 盐值的Base64编码字符串
|
|
|
* @return 生成的密钥
|
|
|
* @throws GeneralSecurityException 安全异常
|
|
|
*/
|
|
|
public static SecretKey generateKey(String password, String salt) throws GeneralSecurityException {
|
|
|
byte[] saltBytes = Base64.decode(salt, Base64.NO_WRAP);
|
|
|
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), saltBytes, ITERATION_COUNT, KEY_LENGTH);
|
|
|
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(KEY_GENERATION_ALGORITHM);
|
|
|
byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
|
|
|
return new SecretKeySpec(keyBytes, "AES");
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 加密文本
|
|
|
* @param plainText 明文文本
|
|
|
* @param password 密码
|
|
|
* @param salt 盐值的Base64编码字符串
|
|
|
* @param iv 初始化向量的Base64编码字符串
|
|
|
* @return 加密后的文本的Base64编码字符串
|
|
|
* @throws GeneralSecurityException 安全异常
|
|
|
*/
|
|
|
public static String encrypt(String plainText, String password, String salt, String iv) throws GeneralSecurityException {
|
|
|
SecretKey key = generateKey(password, salt);
|
|
|
byte[] ivBytes = Base64.decode(iv, Base64.NO_WRAP);
|
|
|
|
|
|
Cipher cipher = Cipher.getInstance(ENCRYPTION_ALGORITHM);
|
|
|
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(ivBytes));
|
|
|
|
|
|
try {
|
|
|
byte[] encryptedBytes = cipher.doFinal(plainText.getBytes("UTF-8"));
|
|
|
return Base64.encodeToString(encryptedBytes, Base64.NO_WRAP);
|
|
|
} catch (UnsupportedEncodingException e) {
|
|
|
// UTF-8 是标准字符编码,不应该抛出此异常
|
|
|
throw new RuntimeException("UTF-8 encoding not supported", e);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 解密文本
|
|
|
* @param encryptedText 加密文本的Base64编码字符串
|
|
|
* @param password 密码
|
|
|
* @param salt 盐值的Base64编码字符串
|
|
|
* @param iv 初始化向量的Base64编码字符串
|
|
|
* @return 解密后的明文文本
|
|
|
* @throws GeneralSecurityException 安全异常
|
|
|
*/
|
|
|
public static String decrypt(String encryptedText, String password, String salt, String iv) throws GeneralSecurityException {
|
|
|
SecretKey key = generateKey(password, salt);
|
|
|
byte[] ivBytes = Base64.decode(iv, Base64.NO_WRAP);
|
|
|
|
|
|
Cipher cipher = Cipher.getInstance(ENCRYPTION_ALGORITHM);
|
|
|
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(ivBytes));
|
|
|
|
|
|
byte[] encryptedBytes = Base64.decode(encryptedText, Base64.NO_WRAP);
|
|
|
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
|
|
|
|
|
|
try {
|
|
|
return new String(decryptedBytes, "UTF-8");
|
|
|
} catch (UnsupportedEncodingException e) {
|
|
|
// UTF-8 是标准字符编码,不应该抛出此异常
|
|
|
throw new RuntimeException("UTF-8 encoding not supported", e);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 生成密码哈希值
|
|
|
* @param password 密码
|
|
|
* @param salt 盐值的Base64编码字符串
|
|
|
* @return 密码哈希值的Base64编码字符串
|
|
|
*/
|
|
|
public static String generatePasswordHash(String password, String salt) {
|
|
|
try {
|
|
|
byte[] saltBytes = Base64.decode(salt, Base64.NO_WRAP);
|
|
|
byte[] passwordBytes = password.getBytes("UTF-8");
|
|
|
|
|
|
// 合并密码和盐值
|
|
|
byte[] combinedBytes = new byte[passwordBytes.length + saltBytes.length];
|
|
|
System.arraycopy(passwordBytes, 0, combinedBytes, 0, passwordBytes.length);
|
|
|
System.arraycopy(saltBytes, 0, combinedBytes, passwordBytes.length, saltBytes.length);
|
|
|
|
|
|
// 计算哈希值
|
|
|
MessageDigest digest = MessageDigest.getInstance(HASH_ALGORITHM);
|
|
|
byte[] hashBytes = digest.digest(combinedBytes);
|
|
|
|
|
|
return Base64.encodeToString(hashBytes, Base64.NO_WRAP);
|
|
|
} catch (NoSuchAlgorithmException e) {
|
|
|
Log.e(TAG, "Error generating password hash", e);
|
|
|
return null;
|
|
|
} catch (UnsupportedEncodingException e) {
|
|
|
Log.e(TAG, "Error generating password hash", e);
|
|
|
return null;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 验证密码
|
|
|
* @param password 待验证的密码
|
|
|
* @param salt 盐值的Base64编码字符串
|
|
|
* @param storedHash 存储的哈希值的Base64编码字符串
|
|
|
* @return 密码是否正确
|
|
|
*/
|
|
|
public static boolean verifyPassword(String password, String salt, String storedHash) {
|
|
|
String generatedHash = generatePasswordHash(password, salt);
|
|
|
return generatedHash != null && generatedHash.equals(storedHash);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 生成密保问题答案的哈希值
|
|
|
* @param answer 密保问题答案
|
|
|
* @param salt 盐值的Base64编码字符串
|
|
|
* @return 答案哈希值的Base64编码字符串
|
|
|
*/
|
|
|
public static String generateAnswerHash(String answer, String salt) {
|
|
|
// 转换为小写并去除空格,提高用户体验
|
|
|
String normalizedAnswer = answer.toLowerCase().trim();
|
|
|
return generatePasswordHash(normalizedAnswer, salt);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 验证密保问题答案
|
|
|
* @param answer 待验证的答案
|
|
|
* @param salt 盐值的Base64编码字符串
|
|
|
* @param storedHash 存储的哈希值的Base64编码字符串
|
|
|
* @return 答案是否正确
|
|
|
*/
|
|
|
public static boolean verifyAnswer(String answer, String salt, String storedHash) {
|
|
|
String normalizedAnswer = answer.toLowerCase().trim();
|
|
|
String generatedHash = generatePasswordHash(normalizedAnswer, salt);
|
|
|
return generatedHash != null && generatedHash.equals(storedHash);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 生成随机密钥(用于测试或特殊情况)
|
|
|
* @return 生成的密钥的Base64编码字符串
|
|
|
* @throws NoSuchAlgorithmException 算法不存在异常
|
|
|
*/
|
|
|
public static String generateRandomKey() throws NoSuchAlgorithmException {
|
|
|
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
|
|
|
keyGenerator.init(KEY_LENGTH);
|
|
|
SecretKey key = keyGenerator.generateKey();
|
|
|
return Base64.encodeToString(key.getEncoded(), Base64.NO_WRAP);
|
|
|
}
|
|
|
}
|