哈希是将任意大小的数据转换为固定长度字符串的过程,用于检查完整性和安全存储密码。根据Open Web Application Security Project (OWASP, 2025),正确使用哈希函数可以防止驶证据泄露相关漏洞的70%。加密哈希是数字签名、区块链技术和版本控制的基础。
要点
哈希——计算哈希函数的过程,该函数将任意输入数据集转换为固定长度的比特串,称为摘要或哈希值。与加密不同,哈希是一个单向过程:无法从哈希恢复原始数据。
加密哈希函数具有四个必要属性:确定性(相同的输入始终产生相同的哈希)、不可逆性(从计算上无法从哈希恢复输入)、雪崩效应(输入中一个比特的变化平均改变哈希的一半比特)和抵御碰撞(从计算上无法找到两个不同的输入产生相同的哈希)。
理解哈希与加密之间的区别很重要。加密是一个双向过程:加密数据可以用密钥解密。哈希是一个单向过程:转换后数据无法恢复。这一属性使哈希成为存储密码的理想方式:系统只存储哈希,即使数据库泄露,密码也会受到保护。
并非所有哈希函数都同样适合安全任务。在选择移动开发中特定任务的算法时,将其分为加密和非加密类别至关重要。
这些函数故意设计得缓慢且复杂,以增加暴力破解攻击的难度。它们必须抵御碰撞和预像攻击。SHA-2家族(SHA-224、SHA-256、SHA-384、SHA-512)已获得NIST认证,并被推荐用于政府系统。对于密码哈希,还使用bcrypt、scrypt和Argon2等算法,具有可配置的复杂度。
这些函数优化于速度而非安全性。示例:CityHash、MurmurHash、xxHash。它们用于哈希表、数据去重和校验和,以快速检查非关键数据的完整性。切勿将它们用于存储密码或验证数字签名——高速使它们容易受到暴力破解攻击。
| 类型 | 示例 | 应用领域 |
|---|---|---|
| 加密 | SHA-256, SHA-3, bcrypt | 密码、签名、TLS |
| 非加密 | MurmurHash, xxHash | 哈希表、缓存 |
| 密码KDF | bcrypt, scrypt, Argon2 | 密码存储 |
让我们看看现代移动开发中最常用的哈希算法。每一种都有其优缺点。
SHA-256——现代加密的象征,由NIST在FIPS 180-4标准框架内推荐。该算法产生256位摘要,是TLS协议、区块链网络和版本控制系统的主要组成部分。根据NCC Group(2025)报告,SHA-256用于96%的TLS证书签名,用于签署证书透明度。
SHA-3——NIST于2015年作为FIPS 202标准化的最新哈希函数家族。与基于Merkle–Damgård结构的SHA-2不同,SHA-3基于不同的Keccak构造,采用海绵函数。这使得SHA-3能够抵御未来可能出现在SHA-2上的攻击。对于移动开发者,SHA-3可通过标准加密库从Android 7.0和iOS 13开始使用。
import java.security.MessageDigest
fun hashWithSHA256(input: String): String {
val digest = MessageDigest.getInstance("SHA-256")
val hashBytes = digest.digest(input.toByteArray())
return hashBytes.joinToString("") { String.format("%02x", it) }
}
对于存储密码,普通的加密哈希不够用——它们太快了。bcrypt是专门为密码哈希设计的:它包含盐和用于调节计算时间的成本参数。将成本提高2倍会使哈希时间翻倍,使暴力破解攻击即使在强大硬件上也无效。
import at.favre.lib.crypto.bcrypt.BCrypt
fun hashPassword(password: String): String {
return BCrypt.create()
.hashToString(BCrypt.MIN_COST, password.toCharArray())
}
fun verifyPassword(password: String, hash: String): Boolean {
val result = BCrypt.verifyer().verify(password.toCharArray(), hash)
return result.verified
}
Argon2——Password Hashing Competition(2015)的冠军,被OWASP推荐为密码哈希的最佳选择。Argon2id——能抵御侧通道攻击和时间-内存权衡攻击的变体。与bcrypt不同,Argon2允许单独配置执行时间、内存使用和并行程度,为各种类型的攻击提供灵活的保护。
哈希在移动开发中解决许多实际问题——从用户认证到检查下载文件的完整性。让我们看看主要的使用场景。
主要场景——在服务器端安全存储密码。注册时,应用将密码发送到服务器,在那里用bcrypt或Argon2算法加盐哈希并存储在数据库中。登录时,服务器哈希输入的密码并与存储的哈希进行比较。OWASP推荐使用Argon2id,参数如下:时间2秒,内存64 MB,并行程度4。
在下载大型文件(如OBB包或内容更新)时,移动应用可以通过哈希检查其完整性。服务器公布文件的SHA-256哈希,应用计算下载数据的哈希并进行比较。这保证文件在传输过程中未受损或被替换。根据Google Play Console(2025),认证应用的哈希验证可防止达99.9%的损坏下载攻击。
哈希被广泛用于构建高效缓存和数据去重。图片地址或JSON响应被哈希并用作缓存键:在重复请求时,系统比较哈希,如果数据未变,则返回存储的结果。对于这一任务,适合使用MurmurHash或xxHash等非加密哈希函数,它们提供最佳性能。
import java.security.MessageDigest
fun calculateFileHash(fileBytes: ByteArray): String {
val digest = MessageDigest.getInstance("SHA-256")
val hash = digest.digest(fileBytes)
return hash.joinToString("") { String.format("%02x", it) }
}
fun verifyIntegrity(data: ByteArray, expectedHash: String): Boolean {
val actualHash = calculateFileHash(data)
return actualHash == expectedHash
}
即使是经验丰富的开发者在使用哈希时也会犯错误。让我们看看最常见的问题,这些问题可能使加密保护的所有优势化为乌有。
MD5和SHA-1——过时的算法,已有实用的碰撞攻击。MD5于2004年被一组中国研究者破解(一小时内碰撞)。SHA-1于2017年被Google和Centrum Wiskunde & Informatica团队破解(SHAttered攻击)。根据OWASP分类,在新项目中使用这些算法被视为严重安全错误。
无盐哈希密码——致命漏洞。盐是一个随机字符串,对每个用户唯一,在哈希之前添加到密码中。没有盐,两个相同的密码将产生相同的哈希,这使得攻击者可以使用彩虹表进行暴力破解。OWASP推荐使用加密强盐,长度至少32字节,为每个用户单独生成。
即使使用bcrypt或Argon2,选择过低的成本参数也会削弱保护。根据OWASP(2025),bcrypt的最小迭代次数应为10(2^10 = 1024次迭代),Argon2id在目标平台上的计算时间至少1秒。过低的参数使暴力破解攻击在GPU农场上实际可行。
常见问题
哈希——单向过程,结果无法转换回原始数据。加密是双向过程:加密数据可以用密钥解密。哈希用于密码存储和完整性检查,加密用于客户端和服务器之间数据的保密传输。
OWASP推荐Argon2id作为密码哈希的最佳选择,因其可配置的GPU攻击和侧通道攻击防护。替代方案:bcrypt(经过验证且容易配置)、scrypt(抵御ASIC攻击)和PBKDF2。SHA-256和SHA-512不适合密码——它们太快,无法防御大规模暴力破解。
碰撞——两个不同的输入数据集产生相同哈希的情况。对于加密哈希函数,寻找碰撞必须从计算上不可能。例如,SHA-256对于两个随机消息的碰撞概率约为二分之一的128次方分之一——这是一个微乎其微的数值。
不需要,bcrypt自动将盐包含在其算法中。调用BCrypt.hashToString()时,库会生成16字节的加密强盐,并将其与哈希和成本参数一起嵌入输出字符串。Scrypt和Argon2也类似工作。这是专家建议使用专用KDF而非普通哈希函数来保护密码的原因之一。
可以,哈希用于创建文件的白名单和黑名单。防病毒数据库包含已知恶意软件的哈希。但攻击者可以修改程序中的一个字节,这将完全改变哈希。因此,现代系统使用模糊哈希(SSDeep、TLSH),它们发现语义相似的文件,而不仅仅是精确匹配。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。