最美老人 發表於 2025-12-3 09:02:49

Go使用SM3哈希算法和随机盐实现用户密码加密

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">前言</a></li><li><a href="#_label1">一、为什么要加盐 (Salt)</a></li><li><a href="#_label2">二、SM3 哈希算法</a></li><li><a href="#_label3">三、Go 实现用户密码加密</a></li><ul class="second_class_ul"><li><a href="#_lab2_3_0">1. 生成随机盐</a></li><li><a href="#_lab2_3_1">2. SM3 哈希函数</a></li><li><a href="#_lab2_3_2">3. 生成哈希密码</a></li><li><a href="#_lab2_3_3">4. 验证密码</a></li><li><a href="#_lab2_3_4">5. 可扩展支持多种算法</a></li></ul><li><a href="#_label4">四、完整示例</a></li><ul class="second_class_ul"></ul><li><a href="#_label5">五、小结</a></li><ul class="second_class_ul"></ul></ul></div><p class="maodian"><a name="_label0"></a></p><h2>前言</h2>
<p>在现代 Web 系统中,<strong>用户密码安全存储</strong>是非常关键的一环。<br />传统做法用简单哈希,容易受到彩虹表攻击。</p>
<p>本文将介绍如何使用 <strong>SM3 哈希算法 + 随机盐 (salt)</strong> 来加密用户密码,并提供 Go 语言实现示例。</p>
<p class="maodian"><a name="_label1"></a></p><h2>一、为什么要加盐 (Salt)</h2>
<p>直接哈希密码存在以下问题:</p>
<p><strong>彩虹表攻击</strong></p>
<ul><li>攻击者可以通过预先计算的哈希表快速破解常用密码。</li></ul>
<p><strong>相同密码哈希值相同</strong></p>
<ul><li>用户 A 和用户 B 使用相同密码,会生成相同哈希值,增加泄露风险。</li></ul>
<p><strong>解决方案</strong>:<br />在密码哈希前增加随机盐 (Salt),每个用户的密码哈希值都不同,即使密码相同也不会重复。</p>
<p class="maodian"><a name="_label2"></a></p><h2>二、SM3 哈希算法</h2>
<p>SM3 是中国国家标准的哈希算法(国密算法),具有:</p>
<ul><li>输出长度 256 位</li><li>安全性高,适用于密码存储</li><li>广泛应用于金融和安全系统</li></ul>
<p>在 Go 中可以使用 <code>github.com/tjfoc/gmsm/sm3</code> 来实现 SM3 哈希。</p>
<p class="maodian"><a name="_label3"></a></p><h2>三、Go 实现用户密码加密</h2>
<p>下面是一套完整的 SM3 密码加密方案:</p>
<p class="maodian"><a name="_lab2_3_0"></a></p><h3>1. 生成随机盐</h3>
<div class="jb51code"><pre class="brush:go;">func generateSalt() (string, error) {
    b := make([]byte, 16) // 16字节随机盐
    if _, err := rand.Read(b); err != nil {
      return "", err
    }
    return hex.EncodeToString(b), nil
}
</pre></div>
<ul><li>使用 <code>crypto/rand</code> 生成安全随机数</li><li>转为十六进制字符串存储</li></ul>
<p class="maodian"><a name="_lab2_3_1"></a></p><h3>2. SM3 哈希函数</h3>
<div class="jb51code"><pre class="brush:go;">func sm3Hash(password, salt string) string {
    h := sm3.New()
    h.Write([]byte(password + salt))
    return hex.EncodeToString(h.Sum(nil))
}
</pre></div>
<ul><li>将密码和盐拼接后进行 SM3 哈希</li><li>返回十六进制字符串</li></ul>
<p class="maodian"><a name="_lab2_3_2"></a></p><h3>3. 生成哈希密码</h3>
<div class="jb51code"><pre class="brush:go;">func HashPassword(password string) (string, error) {
    salt, err := generateSalt()
    if err != nil {
      return "", err
    }
    hash := sm3Hash(password, salt)
    return fmt.Sprintf("sm3$v1$%s$%s", salt, hash), nil
}
</pre></div>
<ul><li>存储格式为:<code>sm3$v1$&lt;salt_hex&gt;$&lt;hash_hex&gt;</code></li><li>包含算法标识、版本、盐和哈希值,便于版本升级和兼容不同算法</li></ul>
<p class="maodian"><a name="_lab2_3_3"></a></p><h3>4. 验证密码</h3>
<div class="jb51code"><pre class="brush:go;">func CheckPassword(dbPassword, inputPassword string) (bool, error) {
    parts := strings.Split(dbPassword, "$")
    if len(parts) != 4 {
      return false, errors.New("hash 格式不正确")
    }
    _, version, salt, hash := parts, parts, parts, parts
    if version != "v1" {
      return false, errors.New("不支持的版本")
    }
    calculatedHash := sm3Hash(inputPassword, salt)
    return calculatedHash == hash, nil
}
</pre></div>
<ul><li>从数据库中取出存储的哈希</li><li>提取盐和版本信息</li><li>对输入密码进行同样哈希并对比</li></ul>
<p class="maodian"><a name="_lab2_3_4"></a></p><h3>5. 可扩展支持多种算法</h3>
<p>代码中还可以支持 <code>bcrypt</code> 或其他哈希算法:</p>
<div class="jb51code"><pre class="brush:go;">func CheckPassword(dbPassword, inputPassword, hashAlgo string) (bool, error) {
    switch hashAlgo {
    case "bcrypt":
      return bcrypt.CompareHashAndPassword([]byte(dbPassword), []byte(inputPassword)) == nil, nil
    case "sm3":
      return CheckPassword(dbPassword, inputPassword)
    default:
      return false, errors.New("未知 hash 算法")
    }
}
</pre></div>
<ul><li>方便未来系统迁移或混合使用多种哈希方案</li></ul>
<p class="maodian"><a name="_label4"></a></p><h2>四、完整示例</h2>
<div class="jb51code"><pre class="brush:go;">func main() {
    password := "123456"
   
    hash, _ := HashPassword(password)
    fmt.Println("存储密码:", hash)
   
    ok, _ := CheckPassword(hash, password)
    fmt.Println("验证密码:", ok)
}
</pre></div>
<p>输出示例:</p>
<div class="jb51code"><pre class="brush:go;">存储密码: sm3$v1$e2a3f4...$9f8d7c...
验证密码: true
</pre></div>
<p class="maodian"><a name="_label5"></a></p><h2>五、小结</h2>
<ul><li><strong>使用 SM3 + 随机盐</strong>,可以安全存储用户密码</li><li><strong>存储格式建议包含算法和版本号</strong>,便于升级</li><li><strong>密码验证时只需从数据库取出盐和哈希值</strong>即可</li><li>结合 Go 的标准库和 <code>gmsm/sm3</code>,可以快速落地</li></ul>
<blockquote><p>使用 SM3 哈希和盐的方式,能有效抵御彩虹表攻击和密码重复泄露风险,是企业级系统密码安全存储的可靠方案。</p></blockquote>
頁: [1]
查看完整版本: Go使用SM3哈希算法和随机盐实现用户密码加密