散列算法是把任意长度的输入(又叫做预映射 pre-image )通过算法变换成固定长度的输出。散列是信息的提炼,通常其长度要比信息小得多,且为一个固定长度。
特性:
- 高效:可以快速计算出哈希值
- 不可逆:从哈希值不能反向推导出原始数据
- 输入敏感:原始数据只要有一点变动,得到的哈希值也会差别很大
- 冲突避免:对于不同的原始数据,哈希值相同的概率非常小
使用场景:
- 安全加密。 例如网络传输密码。
- 唯一标识。例如在海量的图库中,搜索一张图是否存在。可以给每一个图片取一个唯一标识,或者说信息摘要,通过这个唯一标识来判定图片是否在图库中,这样就可以减少很多工作量
- 数据校验。例如下载文件的完整性
- 负载均衡。通过哈希算法,对客户端 IP 地址或者会话 ID 计算哈希值,将取得的哈希值与服务器个数进行取模运算,最终得到的值就是应该被路由到的服务器编号
- ……
常见算法:
- MD4 1990年,输出128位 (已经不安全)
- MD5 1991年,输出128位 (已经不安全)
- SHA-0 1993年,输出160位 (发布之后很快就被撤回,是SHA-1的前身)
- SHA-1 1995年,输出160位 (已经不安全)
- SHA-2 包括 SHA-224、SHA-256、SHA-384,和 SHA-512,分别输出 224、256、384、512位。 (目前安全)
Android Hash 工具类:
@file:JvmName("HashUtil")
import java.io.File
import java.io.FileInputStream
import java.security.MessageDigest
fun String.md5(upperCase: Boolean = true): String = hash("MD5", this, upperCase)
fun String.sha1(upperCase: Boolean = true): String = hash("SHA-1", this, upperCase)
fun String.sha256(upperCase: Boolean = true): String = hash("SHA-256", this, upperCase)
private fun hash(type: String, input: String, upperCase: Boolean = true): String {
val bytes = MessageDigest.getInstance(type).digest(input.toByteArray())
return bytes.asHex(upperCase)
}
fun ByteArray.asHex(upperCase: Boolean = true): String {
val hexChars = if (upperCase) "0123456789ABCDEF" else "0123456789abcdef"
val result = StringBuilder(size * 2)
forEach {
val octet = it.toInt()
result.append(hexChars[octet shr 4 and 0x0f])
result.append(hexChars[octet and 0x0f])
}
return result.toString()
}
fun File.md5(upperCase: Boolean = true): String {
if (!this.exists()) return ""
var fis: FileInputStream? = null
try {
fis = this.inputStream()
val digest = MessageDigest.getInstance("MD5")
val byteArray = ByteArray(1024 * 10)
var readLength = 0
while (fis.read(byteArray).also { readLength = it } != -1) {
digest.update(byteArray, 0, readLength)
}
return digest.digest().asHex(upperCase)
} catch (e: Exception) {
e.printStackTrace()
} finally {
try {
fis?.close()
} catch (e: Exception) {
}
}
return ""
}
iOS Hash 工具类:
extension String {
/// sha1 encoded of string
public var sha1: String {
// https://stackoverflow.com/questions/25761344/how-to-hash-nsstring-with-sha1-in-swift
let data = Data(self.utf8)
var digest = [UInt8](repeating: 0, count:Int(CC_SHA1_DIGEST_LENGTH))
data.withUnsafeBytes {
_ = CC_SHA1($0.baseAddress, CC_LONG(data.count), &digest)
}
let hexBytes = digest.map { String(format: "%02hhx", $0) }
return hexBytes.joined()
}
/// md5 encoded of string
var md5: String {
guard let data = self.data(using: .utf8) else {
return self
}
var digest = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
#if swift(>=5.0)
_ = data.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) in
return CC_MD5(bytes.baseAddress, CC_LONG(data.count), &digest)
}
#else
_ = data.withUnsafeBytes { bytes in
return CC_MD5(bytes, CC_LONG(data.count), &digest)
}
#endif
return digest.map { String(format: "%02x", $0) }.joined().uppercased()
}
/// String encoded in base64 (if applicable).
///
/// "Hello World!".base64Encoded -> Optional("SGVsbG8gV29ybGQh")
///
var base64Encoded: String? {
// https://github.com/Reza-Rg/Base64-Swift-Extension/blob/master/Base64.swift
let plainData = self.data(using: .utf8)
return plainData?.base64EncodedString()
}
}