前言
首先我们来看看过去几年都发生了哪些因为加密技术使用不当或者未加密而导致的安全风险。
KRACK攻击
时间:2017年10月
描述:KRACK攻击利用Wi-Fi加密标准WPA2的漏洞,允许攻击者窃取Wi-Fi网络中的敏感信息,如密码和其他私人数据。
参考文章链接:https://www.krackattacks.com/
漏洞修复文章链接:https://www.zdnet.com/article/krack-attack-patches-heres-where-the-bugs-are-lurking/
WhatsApp远程执行代码漏洞
时间:2019年5月
描述:WhatsApp存在远程执行代码漏洞,攻击者可以通过向受害者发送一个特制的视频文件,来在其设备上执行任意代码。这个漏洞影响了大约1.5亿用户。
参考文章链接:https://thehackernews.com/2019/05/hacking-whatsapp-account.html
漏洞修复文章链接:https://www.whatsapp.com/security/advisories/2019-07-15
Zoom加密漏洞
时间:2020年4月
描述:视频会议应用Zoom存在加密漏洞,攻击者可以轻松地窃取会议中的敏感信息。这个漏洞被广泛利用,引起了广泛的关注和担忧。
参考文章链接:https://thehackernews.com/2020/04/zoom-cybersecurity-hacking.html
COVID-19疫情相关的网络攻击
时间:2020年
描述:COVID-19疫情相关的网络攻击是一系列利用新冠病毒疫情来进行网络攻击的方法。这些攻击包括钓鱼邮件、恶意软件、虚假网站等,旨在窃取用户的个人信息、金融信息等。
Freak漏洞
时间:2020年3月
描述:Freak漏洞是一种利用加密算法中的漏洞来进行攻击的方法。该漏洞允许攻击者破解TLS/SSL连接中的加密,从而窃取传输的敏感信息。
参考文章链接:https://freakattack.com/
漏洞修复文章链接:https://www.us-cert.gov/ncas/alerts/TA15-086A
那么我们如何在移动应用程序开发过程中正确使用加密技术呢?
密码的作用
密码技术是保障网络与信息安全最有效、最可靠、最经济的手段,在信息安全方面发挥着重要作用,保证了信息的机密性、真实性、数据完整性和行为的不可否认性。
保证信息的机密性
信息的机密性是网络与信息安全的主要属性之一,是指确保信息不会泄露给未经授权的个人、计算机和其他实体的性质。
信息是网络空间中最有价值的资产信息,一旦泄露就可能会给国家、社会、行业、团体、个人带来巨大的危害和影响。
现实世界中,要保证信息的机密性,如将一份文件秘密保存或传递,一般可采用加装保护设施、增加警卫人员、藏匿或伪装等手段。而这些手段操作不便捷的同时,需要投入的人力物力大,人为风险因素多。
信息化时代,纸质文件、资料、书籍等信息被编码为计算机电子文件后,虽然大大提高了文件处理、传输和存储的能力,但为保证信息的机密性带来新的挑战。如电子文件被拷贝、截取和传播等诸多行为难以察觉。
密码学中的加密保护技术保证了信息的机密性。使用实现加密的计算机程序对电子文件加密生成形状乱码的密文,有了足够强大的加密算法防护,即使攻击者拦截到密文,也无法从密文中获取有用的信息。而拥有密钥的人则可以使用实现解密的计算机程序从乱码中恢复原始文件,获取正确信息。
信息安全中的访问控制技术采用口令技术防止非法用户进入某个应用系统的数据库,在一定程度上保证了信息的机密性。但这一技术仅相当于在数据库门口增加了“门卫”而数据本身仍然是明文状态,一旦攻击者绕过“门卫”或“门卫”失效,数据库毫无机密性可言。
保证信息的真实性
信息的真实性也是网络与信息安全的主要属性之一,是指保证信息来源可靠、没有被伪造和篡改的性质。
如何鉴别信息的合法性?如何确认真实的身份信息?这些都是网络与信息安全领域非常重要的任务。它们直接影响着社会秩序、生产生活秩序的各个方面。
现实生活中,可以通过相貌、声音、体态等体貌特征来确认人的身份,通过盖章、签字、手印等措施保证消息来源的可靠性。
信息化时代,在开放的网络环境下,身份信息和消息来源可以被伪造,电子信息和文件会被拷贝、截获和重用。
密码学中的安全认证技术解决了信息的真实性等问题。安全认证技术包括数字签名、消息认证码、身份认证协议等,基本思想是:合法用户拥有各自的“秘密信息”,可使用“秘密信息”处理公共信息并获得相应的“印章”用于证明公共信息的真实性。没有相应“秘密信息”的非法用户不能伪造“印章”。
其他可保证真实性的技术,如生物特征技术利用指纹、虹膜等进行身份认证,但它们如果不结合密码技术用于远程认证将非常不安全。
保证数据的完整性
数据完整性是网络与信息安全的又一个重要属性,表示数据是不是未经授权篡改或破坏的性质。
信息化时代带来了前所未有数据量、信息量、文件量等,各行各业都有大量公开传播和存储的数据,保证数据在传输、存储过程中不被篡改的任务艰巨,特别是在维护大量资料库、文件库时,更为艰巨。
现实生活中,可采用签名、盖章等手段保证数据完整性。
信息化时代,电子文档可采用水印技术,保护文件不被篡改,但电子文档完整性不易检测,被修改后很难发现。
对于大量的电子文件保护任务,哈希算法可以轻松实现数据完整性,通过称为摘要的数学过程,计算从文件中唯一的标识文件的特征信息。只要像这样的简短摘要附加到电子文件,就可以验证文件的完整性。要检查文件是否已被修改,只需使用哈希算法计算新摘要,将这个新的摘要与原来附带的摘要进行比对如果两个摘要一样,反之则证明已被修改。
保证行为的不可否认性
不可否认性同样是网络与信息安全的重要属性。
现实生活中发生的行为会留下证据或“集群”作为不可否认的证据。如在签署合同时,一方拒绝签署合同,那么他的签名就可以作为其拒绝行为不可否认性的证据。
信息化时代,如何防止已经在网络上验证的电子合同、电子报表等的不可否认是实现网络与信息安全的重要任务之一。
数字签名技术基于公钥的加密算法可以有效地解决行为的不可否认问题。一旦用户签署了数字签名就不能拒绝或拒绝他们。对解决网络上的纠纷、电子商务的纠纷等问题数字签名是必不可少的工具。虽然计算机、网络和信息系统的日志能在一定程度上证明用户的操作行为,但由于日志容易被伪造和篡改,因此无法保证行为的不可否认性。
加密算法使用不当导致的安全风险
1、不安全的存储:移动客户端可能会将敏感数据(如密码和用户凭证)存储在明文或不安全的格式中,如明文文本文件或不加密的数据库中。
2、加密算法使用不当:加密算法的实现可能存在安全漏洞,如使用弱加密算法或未正确配置加密参数。此外,使用固定的密钥或硬编码密钥也可能导致安全漏洞。
3、未使用HTTPS:未使用HTTPS协议将移动应用程序与服务器之间的通信进行加密,可能会使敏感数据在传输过程中暴露给攻击者。
4、未正确验证证书:客户端应用程序可能会受到中间人攻击,这些攻击者可能会在通信链路上欺骗应用程序,以获取敏感数据。要解决此问题,应用程序需要正确验证服务器证书。
5、反向工程攻击:攻击者可能会对应用程序进行反向工程,以发现应用程序中存在的漏洞,并利用它们对应用程序进行攻击。
6、未更新的软件:移动客户端可能会运行过时的软件版本,这些版本可能包含已知的安全漏洞和弱点,攻击者可以利用这些漏洞进行攻击。
7、不安全的身份验证:使用不安全的身份验证方式,如明文传输密码或使用简单的身份验证方法(如基于cookie的身份验证),可能会导致攻击者轻松窃取用户凭证。
8、不良的代码实践:在代码实现过程中,可能存在逻辑错误和安全漏洞,例如缓冲区溢出、SQL注入、跨站脚本等。
9、未对用户数据进行合适的清除:应用程序可能会将敏感数据留存在本地设备上,例如用户凭证或私密聊天记录,这可能会导致安全隐患。
10、恶意软件攻击:恶意软件可能会通过植入移动应用程序中的恶意代码或通过攻击者拥有的恶意应用程序来攻击移动客户端,以获取敏感数据或控制设备。
通信中安全性威胁
一般的,我们在网络中传输的数据,都可以认为是存在这潜在的风险的。用一句话来概括就是:“任何在网络中传输的明文数据都存在安全性威胁。”
下面就列举下我们通信中面临的四种威胁:
- 第一,中断。攻击者有意破坏和切断他人在网络上的通信,这是对可用性的攻击。
- 第二,截获。属于被动攻击,攻击者从网络上窃听他人的通信内容,破坏信息的机密性。
- 第三,篡改。攻击者故意篡改网络上传送的报文,这是对完整性的攻击。
- 第四,伪造。攻击者伪造信息在网络传送,这是对真实性的攻击。
密钥硬编码风险案例
Android
public static String encrypt(String data){
try {
SecretKeySpec secretKeySpec = new SecretKeySpec("49u5gh249gh24985ghf429gh4ch8f23f".getBytes(),"AES");
Cipher instance = Cipher.getInstance("AES");
instance.init(1, secretKeySpec);
return Base64.encodeToString(instance.doFinal(data.getBytes()),0);
catch(Exception e){
return "";
iOS
import Foundation
import CommonCrypto
fileprivate let aesKey = "gEKC8gte1FvR3oJV"
fileprivate let iv = "MF00KFtxfibUyDWo"
class Crypto {
private static let aesInstance = AESCrypto(key: aesKey, iv: iv)
private init(){
}
static func encrypt(_ string: String) -> String {
return aesInstance.encrypt(string: string).base64EncodedString()
}
static func decrypt(_ data: String) -> String {
return aesInstance.decrypt(data: Data(base64Encoded:data)!)
}
}
fileprivate class AESCrypto {
private let key: Data
private let iv: Data
init(key: String,iv:String){
guard kevcount=kccKeySizeAEs128. let kevData = kev.datalusina: .utf8) else {
preconditionFailure("Error: Failed to set a key")
guard iv.count = kcCBlockSizeAES128,let ivData = iv.data(using: .utf8) else {
preconditionFailure("Error: Failed to set an initial vector")
}
self.key = kevData
self.iv = ivData
}
func encrypt(string: String) -> Data {}
let status = cryptData.withUnsafeMutableBytes { cryptBytes in
data.withUnsafeBytes { dataBytes in
iv.withunsafeBytes { ivBytes in
key.withunsafeBytes { keyBytes in
CCCrypt(option,CCAlgorithm(kCCAlgorithmAES),options,keyBytes.baseAddress, keyLength, ivBytes.baseAddress
}
}
}
Android SDK提供的加密相关API
Android SDK使用的API和JAVA提供的基本相似,由以下部分组成:
- Java Cryptography Architecture:JCA,java加密体系结构;
- Java Cryptography Extension:JCE,Java加密扩展包);
- Java Secure Sockets Extension:JSSE,Java安全套接字扩展包;
- Java Authentication and Authentication Service:JAAS,Java 鉴别与安全服务。
JCA提供基本的加密框架,如证书、数字签名、消息摘要和密钥对产生器,对应的Android API中的以下几个包:
java.security
java.security.acl
java.security.cert
java.security.interfaces
java.security.spec
JCE扩展了JCA,提供了各种加密算法、摘要算法、密钥管理等功能,对应的Android API中的以下几个包:
javax.crypto
javax.crypto.interfaces
javax.crypto.spec
JSSE提供了SSL(基于安全套接层)的加密功能,使用HTTPS加密传输使用,对应的Android API主要是java.net.ssl包中。
JAAS 提供了在Java平台上进行用户身份鉴别的功能。对应的Android API主要在以下几个包:
javax.security.auth
javax.security.auth.login
javax.security.auth.callback
javax.security.auth.x500
它们其实只是一组接口,实际的算法是可由不同的Provider提供,Android API默认的Provider主要是是Bouncy Castle和OpenSSL。 此外Android API还提供了android.security和android.security.keystore(API 23新增)来管理keychain和keystore。
常用算法之:Base64编码
Base64编码算法是一种用64个字符(ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/)来表示任意二进制数据的方法。在计算机网络发展的早期,由于“历史原因”,电子邮件不支持非ASCII码字符,如果要传送的电子邮件带有非ASCII码字符(诸如中文)或者图片,用户收到的电子邮件将会是一堆乱码,因此发明了Base64编码算法。至于为何会乱码?请大家自行Google。在加解密算法中,原始的数据和加密后的数据一般也是二进制数据,为了不传输出错,方便保存或者调试代码,一般需要对加密后的数据进行base64编码。
Android提供了Base64编码的工具类android.util.Base64,可以直接使用,不用自己去实现base64编码的算法了。如:
byte[] output = sha.digest();
String result = Base64.encodeToString(output, Base64.DEFAULT);
开发者建议
base64只是一种编码方式,并不是一种加密算法,不要使用base64来加密数据。
常用算法之:随机数生成器
在Android加密算法中需要随机数时要使用SecureRandom来获取随机数。 如:
SecureRandom sr = new SecureRandom();
byte[] output = new byte[16];
sr.nextBytes(output);
注意不要给SecureRandom设置种子。调用seeded constructor或者setSeed(byte[])是不安全的。SecureRandom()默认使用的是dev/urandom作为种子产生器,这个种子是不可预测的。
开发者建议
- 不要使用Random类来获取随机数。
- 在使用SecureRandom时候,不要设置种子。使用以下函数设置种子都是有风险的:
SecureRandom.SecureRandom(byte[] seed)
SecureRandom.setSeed(long seed)
SecureRandom.setSeed(byte[] seed)
常用算法之:Hash算法
Hash算法是指任意长度的字符串输入,此算法能给出固定n比特的字符串输出,输出的字符串一般称为Hash值。
特点
- 抗碰撞性:寻找两个不同输入得到相同的输出值在计算上是不可行的,需要大量的时间去寻找到具有相同输出的两个输入字符串。
- 不可逆:不可从结果推导出它的初始状态。
抗碰撞性使得Hash算法对原始输入的任意一点更改,都会导致产生不同的Hash值,因此Hash算法可以用来检验数据的完整性。我们经常见到在一些网站下载某个文件时,网站还提供了此文件的hash值,以供我们下载文件后检验文件是否被篡改。 不可逆的特性使Hash算法成为一种单向密码体制,只能加密不能解密,可以用来加密用户的登录密码等凭证。
开发者建议
1、建议使用SHA-256、SHA-3算法:
如使用SHA-256算法对message字符串做哈希:
byte[] input = message.getBytes();
MessageDigest sha = MessageDigest.getInstance("SHA-256");
sha.update(input);
byte[] output = sha.digest();
String result = Base64.encodeToString(output, Base64.DEFFAULT);
2、不建议使用MD2、MD4、MD5、SHA-1、RIPEMD算法来加密用户密码等敏感信息:
这一类算法已经有很多破解办法,例如md5算法,网上有很多查询的字典库,给出md5值,可以查到加密前的数据。
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] md5Bytes = md.digest(str.getBytes());
String result = Base64.encodeToString(md5Bytes,Base64.DEFAULT);
3、不要使用哈希函数做为对称加密算法的签名。
4、注意:当多个字符串串接后再做hash,要非常当心:
如:字符串S,字符串T,串接做hash,记为 H (S||T)。但是有可能发生以下情况。如“builtin||securely” 和 “built||insecurely”的hash值是完全一样的。 如何修改从而避免上述问题产生? 改为H(length(S) || S || T)或者 H(H(S)||H(T))或者H(H(S)||T)。
实际开发过程中经常会对url的各个参数,做词典排序,然后取参数名和值串接后加上某个SECRET字符串,计算出hash值,作为此URL的签名, 如foo=1, bar=2, baz=3 排序后为bar=2, baz=3, foo=1,做hash的字符串为:SECRETbar2baz3foo1,在参数和值之间没有分隔符,则”foo=bar”和”foob=ar”的hash值是一样的,”foo=bar&fooble=baz”和”foo=barfooblebaz”一样,这样通过精心构造的恶意参数就有可能与正常参数的hash值一样,从而骗过服务器的签名校验。
消息认证算法
要确保加密的消息不是别人伪造的,需要提供一个消息认证码(MAC,Message authentication code)。 消息认证码是带密钥的hash函数,基于密钥和hash函数。密钥双方事先约定,不能让第三方知道。
消息发送者使用MAC算法计算出消息的MAC值,追加到消息后面一起发送给接收者。接收者收到消息后,用相同的MAC算法计算接收到消息MAC值,并与接收到的MAC值对比是否一样。
开发者建议
建议使用HMAC-SHA256算法,避免使用CBC-MAC。 HMAC-SHA256例子如下:
//初始化.KeyGenerator
KeyGenerator keyGenerator = KeyGenerator.getInstance("HmacSHA256");
//产生密钥
SecretKey secretKey = keyGenerator.generateKey();
//获取密钥
byte[] key = secretKey.getEncoded();
Log.d(Base64.encodeToString(key,Base64.DEFAULT));
//还原密钥
SecretKey restoreSecretKey = new SecretKeySpec(key,"HmacSHA256");
//实例化MAC
Mac mac = Mac.getInstance(restoreSecretKey.getAlgorithm());
//初始化MAC
mac.init(restoreSecretKey);
//执行摘要
byte[] hmacSHA256Bytes = mac.doFinal(message.getBytes());
result = Base64.encodeToString(hmacSHA256Bytes,Base64.DEFAULT):
对称加密算法
在对称加密算法中,数据发信方将明文(原始数据)和加密密钥一起经过特殊加密算法处理后,使其变成复杂的加密密文发送出去。收信方收到密文后,若想解读原文,则需要使用加密用过的密钥及相同算法的逆算法对密文进行解密,才能使其恢复成可读明文。在对称加密算法中,使用的密钥只有一个,发收信双方都使用这个密钥对数据进行加密和解密,这就要求解密方事先必须知道加密密钥。
该算法的缺点是,如果一旦密钥泄漏,那么加密的内容将都不可信了。
开发者建议
1、建议使用AES算法。
2、DES默认的是56位的加密密钥,已经不安全,不建议使用。
3、注意加密模式不要使用ECB模式。ECB模式不安全,说明问题的经典的三张图片,如下:
明文是:
4、Android 提供的AES加密算法API默认使用的是ECB模式,所以要显式指定加密算法为:CBC或CFB模式,可带上PKCS5Padding填充。AES密钥长度最少是128位,推荐使用256位。
//生成KEY
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(256);
//产生密钥
SecretKey secretKey = keyGenerator.generateKey();
//获取密钥
byte[] keyBytes = secretKey.getEncoded();
Log.d("AES KEY", Base64.encodeToString(keyBytes,0));
//还原密钥
SecretKey key = new SecretKeySpec(keyBytes, "AES");
//加密
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encodeResult = cipher.doFinal(plainText.getBytes());
Log.d("AESencode", Base64.encodeToString(encodeResult, Base64.DEFAULT));
非对称加密
非对称加密算法需要两个密钥:公开密钥(publickey)和私有密钥(privatekey)。公开密钥与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密(这个过程可以做数字签名)。
非对称加密主要使用的是RSA算法。
开发者建议
1、注意密钥长度不要低于512位,建议使用2048位的密钥长度。 使用RSA进行数字签名的算法,如:
//生成密钥
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
RSAPublicKeyrsaPublicKey=(RSAPublicKey)keyPair.getPublic();
RSAPrivateKeyrsaPrivateKey =(RSAPrivateKey)keyPair.getPrivate()
//签名
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = newPKCS8EncodedKeySpec(rsaPrivateKey.getEncoded())
KeyFactory keyFactory = KeyFactory getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(src.getBytes());
byte[] result = signature.sign();
2、使用RSA算法做加密,RSA加密算法应使用Cipher.getInstance(RSA/ECB/OAEPWithSHA256AndMGF1Padding),否则会存在重放攻击的风险。 如:
KeyPairGenerator keyPairGenerator =
KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
RSAPublicKeyrsaPublicKey=(RSAPublicKey)keyPair.getPublic();
RSAPrivateKey rsaPrivateKey=(RSAPrivateKey)keyPair.getPrivate();
//公钥加密
X509EncodedKeySpec x509EncodedKeySpec = newX509EncodedKeySpec(rsaPublicKey.getEncoded()):
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA256AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE,publicKey);
byte[] result =cipher.doFinal(src.getBytes());
...
//私钥解密
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = newPKCS8EncodedKeySpec(rsaPrivateKey.getEncoded());
KeyFactory keyFactory2 = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory2.generatePrivate(pkcs8EncodedKeySpec);
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA256AndMGF1Padding");
cipher.init(Cipher.DECRYPT_MODE,privateKey);
byte[] result2 = cipher.doFinal(result);
加密算法PBE
PBE是一种基于口令的加密算法,其特点是使用口令代替了密钥,而口令由用户自己掌管,采用随机数杂凑多重加密等方法保证数据的安全性。
开发者建议:
使用基于口令的加密算法PBE时,生成密钥时要加盐,盐的取值最好来自SecureRandom,并指定迭代次数。 如:
//初始化盐
mSalt = new byte[SALT_LENGTH_BYTES];
SecureRandom sr = new SecureRandom();
sr.nextBytes(mSalt);
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(KEY_GENERATOR_MODE);
keySpec = new PBEKeySpec(password, salt, KEY_GEN_ITERATION_COUNT, KEY_LENGTH_BITS);
secretKey = secretKeyFactory.generateSecret(keySpec);
总结
几条原则
1、不要自己设计加密算法和协议,使用业界标准的算法。
2、对称加密算法不要使用ECB模式,不建议使用DES算法。
3、要选择合适长度的密钥。
4、要确保随机数生成器的种子具有足够的信息熵。
5、不要使用没有消息认证的加密算法加密消息,无法防重放。
6、当多个字符串拼接后做hash,要非常当心。
7、当给算法加盐取值时不要太短,不要重复。
8、使用初始化向量时IV时,IV为常量的CBC,CFB,GCM等和ECB一样可以重放,即采用上一个消息的最后一块密文作为下一个消息的IV,是不安全的。
9、密钥应遵循的原则 :
- (1)密钥不能为常量,应随机,定期更换,如果加密数据时使用的密钥为常量,则相同明文加密会得到相同的密文,很难防止字典攻击。
- (2)开发同学要防范密钥硬编码的问题。
密钥存储安全性从高到低
- 密钥存储在硬件支持的Android KeyStore中
- 所有密钥都存储在服务器上,并在强身份验证后可用
- 主密钥存储在服务器上,用于加密存储在Android SharedPreferences中的其他密钥
- 密钥存储在Android KeyStore的软件实现中
- 主密钥存储在Android密钥库的软件实现中,用于加密存储在SharedPreferences中的其他密钥
- 所有密钥都存储在SharedPreferences中(不建议)
- 在源代码中硬编码加密密钥(不建议)
- 基于稳定属性的可预测模糊函数或密钥生成函数(不建议)
- 将生成的密钥存储在外部存储(如/sdcard/)(不建议)
参考文章:
https://blog.oversecured.com/Use-cryptography-in-mobile-apps-...
http://www.52im.net/thread-216-1-1.html
推荐阅读:
移动应用人脸识别风险与检测
过某加固Frida检测
某涉黄APP产业链分析
推荐阅读: