加密和解密

前言

这是一个关于如何使用 Go 加密和解密数据的详细示例。使用代码缩短,例如未提及错误处理。错误处理和用户界面的全面工作项目可以在 Github 上找到这里

加密

介绍和数据

此示例描述了 Go 中的完整工作加密和解密。为此,我们需要一个数据。在这个例子中,我们使用自己的数据结构 secret

type secret struct {
    DisplayName       string
    Notes             string
    Username          string
    EMail             string
    CopyMethod        string
    Password          string
    CustomField01Name string
    CustomField01Data string
    CustomField02Name string
    CustomField02Data string
    CustomField03Name string
    CustomField03Data string
    CustomField04Name string
    CustomField04Data string
    CustomField05Name string
    CustomField05Data string
    CustomField06Name string
    CustomField06Data string
}

接下来,我们要加密这样的 secret。完整的工作示例可以在这里找到 (链接到 Github) 。现在,一步一步的过程:

步骤 1

首先,我们需要一种主密码来保护秘密:masterPassword := "PASS"

第 2 步

所有加密方法都使用字节而不是字符串。因此,我们使用来自我们秘密的数据构造一个字节数组。

secretBytesDecrypted := []byte(fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
    artifact.DisplayName,
    strings.Replace(artifact.Notes, "\n", string(65000), -1),
    artifact.Username,
    artifact.EMail,
    artifact.CopyMethod,
    artifact.Password,
    artifact.CustomField01Name,
    artifact.CustomField01Data,
    artifact.CustomField02Name,
    artifact.CustomField02Data,
    artifact.CustomField03Name,
    artifact.CustomField03Data,
    artifact.CustomField04Name,
    artifact.CustomField04Data,
    artifact.CustomField05Name,
    artifact.CustomField05Data,
    artifact.CustomField06Name,
    artifact.CustomField06Data,
))

第 3 步

我们创造了一些盐,以防止彩虹表攻击,参见维基百科saltBytes := uuid.NewV4().Bytes()。在这里,我们使用 UUID v4,这是不可预测的。

第 4 步

现在,我们能够从主密码和随机盐中导出密钥和向量,关于 RFC 2898:

keyLength := 256
rfc2898Iterations := 6

keyVectorData := pbkdf2.Key(masterPassword, saltBytes, rfc2898Iterations, (keyLength/8)+aes.BlockSize, sha1.New)
keyBytes := keyVectorData[:keyLength/8]
vectorBytes := keyVectorData[keyLength/8:]

第 5 步

所需的 CBC 模式适用于整个块。因此,我们必须检查我们的数据是否与完整块对齐。如果没有,我们必须填写它:

if len(secretBytesDecrypted)%aes.BlockSize != 0 {
    numberNecessaryBlocks := int(math.Ceil(float64(len(secretBytesDecrypted)) / float64(aes.BlockSize)))
    enhanced := make([]byte, numberNecessaryBlocks*aes.BlockSize)
    copy(enhanced, secretBytesDecrypted)
    secretBytesDecrypted = enhanced
}

第 6 步

现在我们创建一个 AES 密码:aesBlockEncrypter, aesErr := aes.NewCipher(keyBytes)

第 7 步

我们为加密数据保留了必要的内存:encryptedData := make([]byte, len(secretBytesDecrypted))。在 AES-CBC 的情况下,加密数据具有与未加密数据相同的长度。

第 8 步

现在,我们应该创建加密器并加密数据:

aesEncrypter := cipher.NewCBCEncrypter(aesBlockEncrypter, vectorBytes)
aesEncrypter.CryptBlocks(encryptedData, secretBytesDecrypted)

现在,加密数据在 encryptedData 变量中。

第 9 步

必须存储加密数据。但不仅仅是数据:没有盐,加密数据无法解密。因此,我们必须使用某种文件格式来管理它。在这里,我们将加密数据编码为 base64,参见维基百科

encodedBytes := make([]byte, base64.StdEncoding.EncodedLen(len(encryptedData)))
base64.StdEncoding.Encode(encodedBytes, encryptedData)

接下来,我们定义文件内容和我们自己的文件格式。格式如下:salt[0x10]base64 content。首先,我们储存盐。为了标记 base64 内容的开头,我们存储字节 10。这是有效的,因为 base64 不使用此值。因此,我们可以通过搜索从文件的结尾到文本开头的第一次出现的 10 来找到 base64 的开头。

fileContent := make([]byte, len(saltBytes))
copy(fileContent, saltBytes)
fileContent = append(fileContent, 10)
fileContent = append(fileContent, encodedBytes...)

第 10 步

最后,我们可以编写我们的文件:writeErr := ioutil.WriteFile("my secret.data", fileContent, 0644)

解密

介绍和数据

至于加密,我们需要一些数据来处理。因此,我们假设我们有一个加密文件和提到的结构 secret。目标是从文件中读取加密数据,解密并创建结构实例。

步骤 1

第一步与加密相同:我们需要一种主密码来解密秘密:masterPassword := "PASS"

第 2 步

现在,我们从文件:encryptedFileData, bytesErr := ioutil.ReadFile(filename) 中读取加密数据。

第 3 步

如前所述,我们可以通过分隔符字节 10 分割 salt 和加密数据,从末尾向后搜索:

for n := len(encryptedFileData) - 1; n > 0; n-- {
    if encryptedFileData[n] == 10 {
        saltBytes = encryptedFileData[:n]
        encryptedBytesBase64 = encryptedFileData[n+1:]
        break
    }
} 

第 4 步

接下来,我们必须解码 base64 编码的字节:

decodedBytes := make([]byte, len(encryptedBytesBase64))
countDecoded, decodedErr := base64.StdEncoding.Decode(decodedBytes, encryptedBytesBase64)
encryptedBytes = decodedBytes[:countDecoded]

第 5 步

现在,我们能够从主密码和随机盐中导出密钥和向量,关于 RFC 2898:

keyLength := 256
rfc2898Iterations := 6

keyVectorData := pbkdf2.Key(masterPassword, saltBytes, rfc2898Iterations, (keyLength/8)+aes.BlockSize, sha1.New)
keyBytes := keyVectorData[:keyLength/8]
vectorBytes := keyVectorData[keyLength/8:]

第 6 步

创建 AES 密码:aesBlockDecrypter, aesErr := aes.NewCipher(keyBytes)

第 7 步

为解密数据保留必要的内存:decryptedData := make([]byte, len(encryptedBytes))。根据定义,它与加密数据的长度相同。

第 8 步

现在,创建解密器并解密数据:

aesDecrypter := cipher.NewCBCDecrypter(aesBlockDecrypter, vectorBytes)
aesDecrypter.CryptBlocks(decryptedData, encryptedBytes)

第 9 步

将读取的字节转换为字符串:decryptedString := string(decryptedData)。因为我们需要行,所以拆分字符串:lines := strings.Split(decryptedString, "\n")

第 10 步

构建一个 secret

artifact := secret{}
artifact.DisplayName = lines[0]
artifact.Notes = lines[1]
artifact.Username = lines[2]
artifact.EMail = lines[3]
artifact.CopyMethod = lines[4]
artifact.Password = lines[5]
artifact.CustomField01Name = lines[6]
artifact.CustomField01Data = lines[7]
artifact.CustomField02Name = lines[8]
artifact.CustomField02Data = lines[9]
artifact.CustomField03Name = lines[10]
artifact.CustomField03Data = lines[11]
artifact.CustomField04Name = lines[12]
artifact.CustomField04Data = lines[13]
artifact.CustomField05Name = lines[14]
artifact.CustomField05Data = lines[15]
artifact.CustomField06Name = lines[16]
artifact.CustomField06Data = lines[17]

最后,在 notes 字段中重新创建换行符:artifact.Notes = strings.Replace(artifact.Notes, string(65000), "\n", -1)