这篇文章将描述 RSA 算法的作用,以及我们如何在 Go 中实现它。
RSA (Rivest–Shamir–Adleman) 加密是使用最广泛的安全数据加密算法之一。
它是一种非对称加密算法,也就是说“单向”。在这种情况下,任何人都可以轻松加密一段数据,但只有拥有正确“密钥”的人才能解密它。

简介RSA加密
RSA 通过生成公钥和私钥来工作。公钥和私钥一起生成,形成一对密钥。

公钥可以用于加密任意的数据片段,但无法解密它。

data -----------|
                |
                -----RSA 加密-----加密数据
                |
public key -----|

私钥可以用于解密由其对应的公钥加密的任何数据片段。

加密数据 -------|
                |
                -----RSA 解密-----data
                |
private key ----|

这意味着我们可以将我们的公钥交给任何我们想要的人。然后他们可以加密他们想要发送给我们的任何信息,而访问此信息的唯一方法是使用我们的私钥来解密它。

密钥生成
我们要做的第一件事是生成公钥和私钥对。这些密钥是随机生成的,并将用于所有后续操作。

我们使用 crypto/rsa 标准库生成密钥,使用 crypto/rand 库生成随机数。

// GenerateKey方法接受一个返回随机位的读取器以及位数
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
    panic(err)
}

// 公钥是 *rsa.PrivateKey 结构的一部分
publicKey := privateKey.PublicKey

// 使用公钥和私钥
// ...

publicKey 和 privateKey 变量将分别用于加密和解密。

加密
我们将使用 EncryptOAEP 方法对任意消息进行加密。我们必须为该方法提供一些输入:

1.选择一个哈希函数,即使输入稍微改变,输出哈希也会完全改变。 SHA256算法适用于此
2.用于生成随机位的随机读取器,以便相同的输入不会给出相同的输出
3.之前生成的公钥
4.要加密的消息
5.可选的标签(在这种情况下我们将省略)

encryptedBytes, err := rsa.EncryptOAEP(
    sha256.New(),
    rand.Reader,
    &publicKey,
    []byte("超级机密信息"),
    nil)
if err != nil {
    panic(err)
}

fmt.Println("加密字节:", encryptedBytes)

这将打印出加密的字节,它们看起来或多或少像垃圾。

解密

要访问加密字节中包含的信息,需要对其进行解密。

我们解密它们的唯一方法是使用与我们加密它们的公钥相对应的私钥。

*rsa.PrivateKey 结构带有一个 Decrypt 方法,我们将使用它从加密数据中获取原始信息。

我们必须为解密提供的数据是:
1.加密数据(称为密文)
2.我们用于加密数据的哈希

// 第一个参数是可选的随机数据生成器(之前使用的rand.Reader)
// 我们可以将该值设置为nil
// 最后的OAEPOptions表示我们使用OAEP加密数据,并且我们使用
// SHA256对输入进行哈希。
decryptedBytes, err := privateKey.Decrypt(nil, encryptedBytes, &rsa.OAEPOptions{Hash: crypto.SHA256})
if err != nil {
    panic(err)
}

// 我们以字节形式获得原始信息,然后将其转换为字符串并打印
fmt.Println("解密消息:", string(decryptedBytes))

签名和验证

RSA密钥还用于签名和验证。签名与加密不同,它使您能够断言真实性,而不是机密性。
这意味着与加密中掩盖原始消息内容(类似于在签名中所做的操作)不同,签名是从消息生成了一段数据,称为“签名”。

data -----------|
                |
                -----RSA 签名-----signature
                |
private key ----|

任何拥有签名、消息和公钥的人都可以使用RSA验证来确保消息确实来自其公钥发出的一方。如果数据或签名不匹配,则验证过程失败。

data -----------|
signature ------|
                -----RSA 验证-----验证通过/失败
                |
public key -----|

请注意,只有拥有私钥的一方才能对消息进行签名,但任何拥有公钥的人都可以验证。

msg := []byte("可验证的消息")

// 在签名之前,我们需要对消息进行哈希处理
// 哈希就是我们实际上要签名的内容
msgHash := sha256.New()
_, err = msgHash.Write(msg)
if err != nil {
    panic(err)
}
msgHashSum := msgHash.Sum(nil)

// 为了生成签名,我们提供一个随机数生成器,
// 我们的私钥,我们使用的哈希算法以及我们消息的哈希总和
signature, err := rsa.SignPSS(rand.Reader, privateKey, crypto.SHA256, msgHashSum, nil)
if err != nil {
    panic(err)
}

// 要验证签名,我们提供公钥,哈希算法
// 消息的哈希总和以及我们之前生成的签名
// 这里有一个可选的“options”参数,我们现在可以忽略
err = rsa.VerifyPSS(&publicKey, crypto.SHA256, msgHashSum, signature, nil)
if err != nil {
    fmt.Println("无法验证签名:", err)
    return
}
// 如果我们从“VerifyPSS”方法中没有得到任何错误,那么意味着我们的
// 签名是有效的
fmt.Println("签名验证成功")

总结
在本文中,我们看到了如何生成RSA公钥和私钥以及如何使用它们来加密、解密、签名和验证任意数据。

在使用这些数据之前,您应该了解一些限制。首先,您要加密的数据应该比密钥的位强度要短。例如,EncryptOAEP文档中说:“消息长度不能超过公共模数的长度减去两倍哈希长度,再减去2。”

示例所使用的哈希算法也应该适合您的用例。 SHA256(在此处的示例中使用)被认为足以满足大多数用例,但对于要处理更多数据的关键应用程序,您可能需要考虑类似 SHA512 等方法。

来源:https://blog.axiaoxin.com/post/2023-06-15-golang-%E5%AE%9E%E7%8E%B0-rsa-%E5%8A%A0%E5%AF%86%E7%AD%BE%E5%90%8D/