常见编码与哈希:Base64 / MD5 / SHA

本节目标

先纠正一个常见误解:Base64 不是加密,只是编码(为了在不支持二进制的场景传输);MD5/SHA 是哈希(摘要),不可逆,常用来做签名/校验。

// 运行环境:Node.js 18+(无需依赖,用内置 crypto)
// 运行:node jsr-l7.cjs
const crypto = require('crypto')

// 1) Base64 编码(不是加密!)
const b64 = Buffer.from('hello').toString('base64')
console.log('base64:', b64, '-> 解码:', Buffer.from(b64, 'base64').toString())

// 2) MD5(哈希,32 位十六进制,不可逆)
const md5 = crypto.createHash('md5').update('hello').digest('hex')
console.log('md5:', md5)

// 3) SHA256(更安全的哈希,64 位)
const sha = crypto.createHash('sha256').update('hello').digest('hex')
console.log('sha256:', sha)

// 实战:前端常把 sign 算成 md5(参数拼接+密钥)
function sign(params, secret) {
  const raw = Object.keys(params).sort().map((k) => k + '=' + params[k]).join('&') + secret
  return crypto.createHash('md5').update(raw).digest('hex')
}
console.log('sign:', sign({ a: '1', b: '2' }, 'KEY'))

逆向识别:看到参数值是 aGVsbG8=(含 =、字符集限定)多半 Base64;看到 32 位十六进制串想 MD5,64 位想 SHA256。但光看长度不能 100% 确定,要和代码逻辑对上。

名词解释

编码(Encoding):把数据从一种「表示形式」换成另一种(如二进制 ↔ 文本),可逆、无密钥。Base64 只是为了让二进制能在文本协议里传输,它不是保密手段。逆向里 Base64 一解码就现原形。

哈希 / 摘要(Hash):把任意长度输入「揉成」固定长度的输出,且几乎不可逆、输入微调输出大变。MD5/SHA 常用于「校验完整性」和「生成签名」。逆向里如果看到 createHash('md5') 或密文长度 32,基本就是它。

课后练习

练习 1:为什么「前端用 MD5 给密码做『加密』」其实不安全?

答案:MD5 是哈希不是加密,且前端代码任何人可见,密钥也暴露在 JS 里;再加上 MD5 已被证明可碰撞。真正密码保护应在服务端用「加盐慢哈希(bcrypt)」。前端 MD5 只适合「签名/校验」,不适合「保密」。

练习 2:一段值 5L2g5aW9 很可能是什么?怎么验证?

答案:长度 8、字符集像 Base64,大概率是 Base64 编码。验证:Buffer.from('5L2g5aW9','base64').toString() 解出 你好 即确认。Base64 的特点是「解码即明文」,所以一眼可疑就先试解码。

总结

「编码 / 哈希 / 加密」三件套是逆向的 ABC,但大多数人第一步就混。我的观点:Base64 千万别叫它加密——它连门锁都算不上,只是把东西换个盒子装,谁都能打开。MD5/SHA 是「指纹」不是「保险箱」:你能用它证明「这段数据没被改」,但还原不出原文(所以逆向遇到纯哈希通常只能「复现计算」,不能「反推明文」)。识别它们的口诀很简单:看到 = 结尾的长串先试 Base64 解码;看到 32/64 位十六进制就去找 createHash。先归类、再读码,逆向效率翻倍。