小言_互联网的博客

保护数据库信息,如何用Go语言+对称密钥做数据加密?

371人阅读  评论(0)

作者 | Purnaresa Yuliartanto

译者 | 凯隐

编辑 | Jane

出品 | 区块链大本营(ID:blockchain_camp)

个人识别信息(PII)是客户告知服务提供商(电子商务、金融服务等)的个人信息。作为服务提供者,他们有责任妥善保管信息。针对PII的攻击可能来自外部,也可能来自服务商内部。

 

为了抵御针对PII的攻击,将存储在数据库中的PII加密,这样组织内的员工就无法读取信息,外部攻击者在设法窃取数据库时也无法读取信息。

 

如何解决这一问题呢?本文就将为大家介绍用 Go语言为数据加密的策略。

 

一、数据写入

 

1、读取输入信息(明文)

2、将明文加密为密文

3、将密文写入到数据库中

 

二、数据读取

 

1、从数据库中读取密文

2、将密文解密为明文

3、发送明文

 

三、加密算法

 

对称密钥非常适合该任务的应用场景,其具有以下特点:

 

1、加密过程发生在一个缔约方(准确地说是同一服务)。因此无需与另一方交换密钥,双方使用同一密钥。

 

2、与非对称加密相比,对称加密速度更快,其带来的额外速度在数据交互服务总是受欢迎的。

 

3、每个数据字段中的文本可能很大。对称加密在加密大数据时具有更好的性能。

 

加密示例

kingsman ==> sLR9ctALjY0rtAi8IvosScCtBE21gyMOBl3xHzi52Hbo+H3O

 

四、示例

 

我们将以金融服务提供商的注册模块为例,展示如何对数据进行简单的加密保存和解密读取。

 

1、新用户创建函数


   
  1. func createData( id, name, nationalID, createTimeUnix string) (err error) {
  2. _, err = DB.Exec(`
  3. INSERT INTO
  4. user ( id, name, national_id, create_time_unix)
  5. VALUES
  6. ( "?", "?", "?", "?")
  7. `, id, name, nationalID, createTimeUnix)
  8. return
  9. }

上面的函数是用Go语言将数据写入数据库的最小函数。

 

这里将示例中的信息范围限制在4个属性:id,姓名,nationalID,createtimeunix。之后会用这个函数来集成加密函数。

 

2、数据读取函数


   
  1. type User struct {
  2. ID string
  3. Name string
  4. NationalID string
  5. CreateTimeUnix string
  6. }
  7. func readData(id string) (user User, err error) {
  8. row := DB.QueryRow( `
  9. SELECT
  10. id, name, national_id, create_time_unix
  11. FROM
  12. user
  13. WHERE
  14. id = "?"`, id)
  15. err = row.Scan(
  16. &user.ID,
  17. &user.Name,
  18. &user.NationalID,
  19. &user.CreateTimeUnix)
  20. return
  21. }

上述代码定义了用户对象的结构体,以及一个从数据库中读取数据的函数。函数是读取数据并将其解析为对象而无需任何数据处理的最小代码。如果提供了数据库中正确的表,该函数将正常工作。我们将在之后用这个函数来解密数据库中的数据。

 

3、数据加密函数


   
  1. func encrypt(plaintext, passphrase string) (chipertext string, err error) {
  2. block, _ := aes.NewCipher([] byte(passphrase))
  3. gcm, err := cipher.NewGCM(block)
  4. if err != nil {
  5. return
  6. }
  7. nonce := make([] byte, gcm.NonceSize())
  8. if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
  9. return
  10. }
  11. ciphertextByte := gcm.Seal(
  12. nonce,
  13. nonce,
  14. [] byte(plaintext),
  15. nil)
  16. chipertext = base64.StdEncoding.EncodeToString(ciphertextByte)
  17. return
  18. }

上面的函数(encrypt)是用于加密纯文本的函数。AES是一种对称加密算法,大部分现代计算机语言(Go,NodeJS,PYTHON,PHP)都含有AES算法的支持库。上面代码的主要含义是:

 

(1)使用NewCipher函数来创建明文(passphrase)的密码(加密器),该函数接受可阅读形式的明文作为输入。

 

(2)使用密封函数(Seal)来加密纯文本,Seal函数的输出是字节格式的密文,是不可阅读的形式,需要将密文编码为base64格式,以便存储在数据库中。

 

我们将使用encrypt函数对nationalID进行加密,该过程发生在createData函数中。

 

4、数据解密函数

 

我们需要创建一个解密函数来对存储在数据库中的数据进行解密,由于是对称密钥,因此解密使用的密钥和加密相同。函数如下:


   
  1. func decrypt(cipherText, key string) (plainText string, err error) {
  2. // prepare cipher
  3. keyByte := [] byte(key)
  4. block, err := aes.NewCipher(keyByte)
  5. if err != nil {
  6. return
  7. }
  8. gcm, err := cipher.NewGCM(block)
  9. if err != nil {
  10. return
  11. }
  12. nonceSize := gcm.NonceSize()
  13. //
  14. // process ciphertext
  15. ciphertextByte, _ := base64.StdEncoding.DecodeString(cipherText)
  16. nonce, ciphertextByteClean := ciphertextByte[:nonceSize], ciphertextByte[nonceSize:]
  17. plaintextByte, err := gcm.Open(
  18. nil,
  19. nonce,
  20. ciphertextByteClean,
  21. nil)
  22. if err != nil {
  23. log.Println(err)
  24. return
  25. }
  26. plainText = string(plaintextByte)
  27. //
  28. return
  29. }

上面的函数(decrypt)主要功能是将密文和密钥重新处理为明文。该函数主要由两部分构成:

 

(1)第一部分是使用Go语言中的AES和cipher库准备密码。该过程需要使用加密过程中使用的密钥。

 

(2)第二部分主要进行解密。数据库中的数据是base64格式的字符串类型。在运行Open函数之前,我们需要将其格式转换为字节类型。Open函数的输出是字节类型的,因此我们需要将它格式化为string类型,这样就得到了可阅读的明文。

 

5、单元测试

 

在我们将加密和解密函数集成到CRUD DB函数之前,我们必须通过单元测试来验证解密函数的输出是否与明文相同:


   
  1. func Test_encrypt(t *testing.T) {
  2. type args struct {
  3. plaintext string
  4. key string
  5. }
  6. tests := [] struct {
  7. name string
  8. args args
  9. }{
  10. {
  11. name: "happy test",
  12. args: args{
  13. plaintext: "kingsman",
  14. key: "04076d64bdb6fcf31706eea85ec98431"},
  15. },
  16. }
  17. for _, tt := range tests {
  18. t.Run(tt.name, func(t *testing.T) {
  19. // encrypt the plaintext
  20. ciphertext, err := encrypt(tt.args.plaintext, tt.args.key)
  21. if err != nil {
  22. t.Errorf( "encrypt() error = %v", err)
  23. return
  24. }
  25. t.Logf( "ciphertext = %s", ciphertext)
  26. //
  27. // decrypt the ciphertext from previous encrypt function
  28. plaintext, err := decrypt(ciphertext, tt.args.key)
  29. if err != nil {
  30. t.Errorf( "encrypt() error = %v", err)
  31. return
  32. }
  33. t.Logf( "plaintext = %s", plaintext)
  34. //
  35. // compare the initial plaintext with output of previous decrypt function
  36. if plaintext != tt.args.plaintext {
  37. t.Errorf( "plaintext = %v, want %v", plaintext, tt.args.plaintext)
  38. }
  39. //
  40. })
  41. }
  42. }

首先通过加密函数(encrypt)来对明文(“kingsman”)进行加密。然后将加密函数输出的密文再输入到解密函数中,预期的输出是等于明文的字符串类型值。最后,我们将输出结果与纯文本进行对比验证:


   
  1. db-encryption go test -v -timeout 30s
  2. === RUN Test_encrypt
  3. === RUN Test_encrypt/happy_test
  4. --- PASS: Test_encrypt ( 0.00s)
  5. --- PASS: Test_encrypt/happy_test ( 0.00s)
  6. main_test. go: 78: ciphertext = sLR9ctALjY0rtAi8IvosScCtBE21gyMOBl3xHzi52Hbo+H3O
  7. main_test. go: 87: plaintext = kingsman
  8. PASS
  9. ok github.com/purnaresa/secureme/db-encryption 0.005s

6、函数集成

 

当我们确信加密和解密函数都能正常工作时,就该将他们集成到CRUD DB函数了。基本上数据在保存到数据库之前都需要进行加密。


   
  1. func createData( id, name, nationalID, createTimeUnix string) (err error) {
  2. // encryption
  3. nationalID, _ = encrypt(nationalID, masterKey)
  4. //
  5. _, err = DB.Exec(`
  6. INSERT INTO
  7. user ( id, name, national_id, create_time_unix)
  8. VALUES
  9. ( "?", "?", "?", "?")
  10. `, id, name, nationalID, createTimeUnix)
  11. return
  12. }

在上面的代码中,我们为nationalID进行加密,为了简化这个例子,我们忽略了错误输出。对于解密,我们必须在从数据库读取数据后立即运行decrypt函数。


   
  1. func readData( id string) ( user User, err error) {
  2. row := DB. QueryRow(`
  3. SELECT
  4. id, name, national_id, create_time_unix
  5. FROM
  6. user
  7. WHERE
  8. id = "?"`, id)
  9. err = row. Scan(
  10. &user.ID,
  11. &user.Name,
  12. &user.NationalID,
  13. &user.CreateTimeUnix)
  14. // decryption
  15. user.NationalID, _ = decrypt(user.NationalID, masterKey)
  16. //
  17. return
  18. }

总结

 

上述代码中提供的是对单个值进行加密的示例,同样的方法也可以用于多个值的加密。只要密钥得到很好的保护(不泄露),AES机制就可以安全使用。上述方案被认为是数据库信息安全的最低要求,因为它只实现了两个基本要素(算法和密钥)。

 

此外,我们可以使用更多的方法来确保数据安全,比如salt,这样即使密钥被盗,攻击者也不能利用密钥来解密密文。

 

完整代码:

https://github.com/purnaresa/secureme/blob/master/db-encryption/main.go

 

原文链接:

https://medium.com/swlh/securing-information-in-database-using-data-encryption-written-in-go-4b2754214050

【END】

推荐阅读

春节加班,老铁们求在看!????


转载:https://blog.csdn.net/Blockchain_lemon/article/details/104140455
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场