概述
在Android开发中,我们有时候可能需要将一些密码或者比较静态字符串放到APP里面,怎样保证这些数据的安全性呢?
通常我们会将这些数据进行加密处理,那加密之后的数据存放到APP的什么地方呢?
1、密钥本地存放
保存加密数据的方式 | 安全性 |
直接放到sharedprefence中 | 安全性最低 |
直接编码到java文件中 | 不安全,dex很容易被逆向 |
密码分成几段,存储在不同地方,例如文件、代码等 | 只要多花时间也可以被逆向 |
ndk开发,密钥放在so文件,加密解密操作放到so文件里 | 提高了安全性,但是有经验的也会逆向 |
gradle中配置build变量 | 不安全,dex很容易被逆向,但避免用户的恶意删除 |
string.xml中 | 不安全,虽然该string已经对应的数字串,但是逆向之后还是可以被跟踪到 |
综上,并没有完全的安全性,只是增大了逆向成本。所以为了保证密钥的安全性,可以通过多种方式混合,增加逆向难度。
不推荐使用文件、数据库来保存静态密钥,容易被用户恶意删除。
2、Base64编码算法
64个字符来表示任意二进制数据。只是一种编码方式,通常为了防止传输出错,对加密后的数据进行base64编码。
private void base64(){
String base = "Base64编码难道很长编码出来的才长吗";
byte[] baseByte = base.getBytes();
String result = Android.util.Base64.encodeToString(baseByte,Base64.DEFAULT);
}
Base64类里面的几种类型
DEFAULT | 默认方式,当字符串过长(一般超过76)会在中间加一个换行符,字符串最后也会增加一个换行符 |
NO_PADDING | 转码时会对字符串长度余4的=部位,该类型就是省略加密字符串最后的= |
NO_WRAP | 去掉所有的换行符,Android通常建议使用该方式 |
CRLF | 使用CR LF这一对作为一行的结尾而不是Lnuix风格的LF |
URL_SAFE | 转码时会生成+/=特殊字符,该类型就是不生成URL和文件名有特殊意识的字符来作为加密字符,具体以-_取代+/ |
针对例子中的字符串,不同的类型编码出来
DEFAULT | QmFzZTY057yW56CB6Zq+6YGT5b6I6ZW/57yW56CB5Ye65p2l55qE5omN6ZW/5ZCX | 会含有+/,同时前后都有换行符 |
NO_PADDING | ||
NO_WRAP | QmFzZTY057yW56CB6Zq+6YGT5b6I6ZW/57yW56CB5Ye65p2l55qE5omN6ZW/5ZCX | 换行符没有了 |
CRLF | ||
URL_SAFE | QmFzZTY057yW56CB6Zq-6YGT5b6I6ZW_57yW56CB5Ye65p2l55qE5omN6ZW_5ZCX | 用-_代替了+/ |
3、随机数生成器
在Android开发中使用SecureRandom来获取随机数,不要使用Random。但在使用过程中不要设置种子
private void random(){
SecureRandom secureRandom = new SecureRandom();
Log.d(TAG,"random = "+secureRandom.nextInt()) ;
}
4、几种常见的加密
不可逆 | 数字摘要:通过算法将短数据变为长数据,标示数据的唯一性。 | md5:用来加密密码 | |
sha1:比md5更长,更安全,效率要低 | |||
可逆 | 对称加密:密钥可以指定,只有一把密钥,如果密钥泄漏数据就会暴漏。 | DES/AES | 加密速度快、但安全性低,只要密钥暴漏,数据就可以被解密 |
非对称加密:两把密钥,有程序生成,不能指定 | RSA | 加密速度慢,但安全性比较高。 公钥加密只能私密解密;私钥加密只能公密解密。 |
1)MD5加密代码
private String md5(String string) {
MessageDigest md5 = null;
try {
md5 = MessageDigest.getInstance("MD5");
byte[] bytes = md5.digest(string.getBytes());
return byteToHex(bytes);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
private String byteToHex(byte[] bytes) {
String result = "";
if (bytes == null || bytes.length == 0) {
return result;
}
for (byte b : bytes) {
//byte的大小为8bits而int的大小为32bits,将bit转换成16进制
String temp = Integer.toHexString(b & 0xff);
if (temp.length() == 1) {
temp = "0" + temp;
}
result += temp;
}
return result;
}
2)SHA1加密
private String sha1(String string) {
MessageDigest sha1 = null;
try {
sha1 = MessageDigest.getInstance("SHA-1");
byte[] bytes = sha1.digest(string.getBytes());
return byteToHex(bytes);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
对比MD5和SHA1对123456的加密
MD5 | e10adc3949ba59abbe56e057f20f883e |
SHA1 | 7c4a8d09ca3762af61e59520943dc26494f8941b |
显然,SHA1的生成的字符串要比MD5要长。
3)DES加密
/**
* Description 根据键值进行加密,返回字符串进行Base64编码
*
* @param data
* @param key 加密键
* @return
* @throws Exception
*/
public static String encryptToBase64(byte[] data, String key) throws Exception {
return Base64.encodeToString(encrypt(data, key), Base64.NO_WRAP);
}
/**
* Description 根据键值进行解密
*
* @param data 该data被base64编码
* @param key 加密键byte数组
* @return
* @throws IOException
* @throws Exception
*/
public static String decryptInBase64(String data, String key) throws Exception {
return new String(decrypt(Base64.decode(data, Base64.NO_WRAP), key));
}
/**
* Description 根据键值进行加密
*
* @param data
* @param key 加密键
* @return
* @throws Exception
*/
public static byte[] encrypt(byte[] data, String key) throws Exception {
byte[] bt = encrypt(data, key.getBytes());
return bt;
}
/**
* Description 根据键值进行解密
*
* @param data
* @param key 加密键byte数组
* @return
* @throws IOException
* @throws Exception
*/
public static byte[] decrypt(byte[] data, String key) throws IOException,
Exception {
if (data == null)
return null;
byte[] bt = decrypt(data, key.getBytes());
return bt;
}
/**
* Description 根据键值进行加密
*
* @param data
* @param key 加密键byte数组
* @return
* @throws Exception
*/
private static byte[] encrypt(byte[] data, byte[] key) throws Exception {
// 生成一个可信任的随机数源
SecureRandom sr = new SecureRandom();
// 从原始密钥数据创建DESKeySpec对象
DESKeySpec dks = new DESKeySpec(key);
// 创建一个密钥工厂,然后用它把DESKeySpec转换成SecretKey对象
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey securekey = keyFactory.generateSecret(dks);
// Cipher对象实际完成加密操作
Cipher cipher = Cipher.getInstance("DES");
// 用密钥初始化Cipher对象
cipher.init(Cipher.ENCRYPT_MODE, securekey, sr);
return cipher.doFinal(data);
}
/**
* Description 根据键值进行解密
*
* @param data
* @param key 加密键byte数组
* @return
* @throws Exception
*/
private static byte[] decrypt(byte[] data, byte[] key) throws Exception {
// 生成一个可信任的随机数源
SecureRandom sr = new SecureRandom();
// 从原始密钥数据创建DESKeySpec对象
DESKeySpec dks = new DESKeySpec(key);
// 创建一个密钥工厂,然后用它把DESKeySpec转换成SecretKey对象
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey securekey = keyFactory.generateSecret(dks);
// Cipher对象实际完成解密操作
Cipher cipher = Cipher.getInstance("DES");
// 用密钥初始化Cipher对象
cipher.init(Cipher.DECRYPT_MODE, securekey, sr);
return cipher.doFinal(data);
}
当然也可以直接用new String(byte[])的方式将加密和解密后的bytes输出
4)AES加密
/**
* Description 根据键值进行加密
*
* @param data
* @param key 加密键
* @return
* @throws Exception
*/
public static byte[] encrypt(String data, String key) throws Exception {
byte[] bt = encrypt(data.getBytes(), key);
return bt;
}
/**
* Description 根据键值进行加密
*
* @param data
* @param key 加密键byte数组
* @return
* @throws Exception
*/
private static byte[] encrypt(byte[] data, String key) throws Exception {
//创建密钥
SecretKeySpec securekey = getKey(key);
// Cipher对象实际完成加密操作
Cipher cipher = Cipher.getInstance("AES");
// 用密钥初始化Cipher对象
cipher.init(Cipher.ENCRYPT_MODE, securekey);
return cipher.doFinal(data);
}
/**
* Description 根据键值进行解密
*
* @param data
* @param key 加密键byte数组
* @return
* @throws Exception
*/
public static byte[] decrypt(byte[] data,String key) throws Exception {
//创建密钥
SecretKeySpec keySpec = getKey(key);
// Cipher对象实际完成解密操作
Cipher cipher = Cipher.getInstance("AES");
// 用密钥初始化Cipher对象
cipher.init(Cipher.DECRYPT_MODE, keySpec);
byte[] result = cipher.doFinal(data);
return result;
}
private static SecretKeySpec getKey(String key) {
//必须必须要注意的是,这里的password的长度,必须为128或192或256bits.
// 也就是16或24或32bytebyte[] password = new byte[16/24/32]; "123456781234567812345678"
return new SecretKeySpec(key.getBytes(), "AES/CBC/PKCS5PADDING");
}
重点说下获取这个加密和解密的key的时候遇到的问题。
一开始采用的是下面这种方式:
private static SecretKeySpec getKey() {
KeyGenerator keyGenerator = null;
try {
keyGenerator = KeyGenerator.getInstance("AES");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
keyGenerator.init(128, new SecureRandom("12345678".getBytes()));
SecretKey secretKey = keyGenerator.generateKey();
byte[] enCodeFormat = secretKey.getEncoded();
SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
return key;
}
在Android环境下运行,报错
javax.crypto.BadPaddingException: pad block corrupted
通过查原因是由于加密和解密的两个key不一致引起的,通过加日志,的确在加密和解密的时候,产生的SecretKeySpec是不一样的。
所以采用了示例中生成key的方式,但是该方式还是要注意一个问题,就是在设置密码的时候,,必须为128或192或256bits,也就是16或24或32bytebyte[] password = new byte[16/24/32]; 否则会抛出下面的错误
最后
以上就是欢呼小刺猬为你收集整理的Android的本地密钥的安全性的全部内容,希望文章能够帮你解决Android的本地密钥的安全性所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复