加密和解密

前言

這是一個關於如何使用 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)