使用AES和Base64加密字符串

39

我正在尝试在数据库中加密一些文本,以便在程序启动期间加载和解密。

我尝试了几种方法,包括第三方库https://github.com/richard-lyman/lithcrypt,但都没有成功。使用以下方法加密/解密8/10项,但似乎在加密/解密过程中留下了一些填充残留物。我的代码如下:

package client                                                                                                                                                                                              
import (                                                                                                                                                                                                    
    "encoding/base64"                                                                                                                                                                                       
    "crypto/aes"                                                                                                                                                                                            
    "crypto/cipher"                                                                                                                                                                                         
    "fmt"                                                                                                                                                                                                   
) 

var iv = []byte{34, 35, 35, 57, 68, 4, 35, 36, 7, 8, 35, 23, 35, 86, 35, 23}

func encodeBase64(b []byte) string {                                                                                                                                                                        
    return base64.StdEncoding.EncodeToString(b)                                                                                                                                                             
}                                                                                                                                                                                                           

func decodeBase64(s string) []byte {                                                                                                                                                                        
    data, err := base64.StdEncoding.DecodeString(s)                                                                                                                                                         
    if err != nil { panic(err) }                                                                                                                                                                            
    return data                                                                                                                                                                                             
}                                                                                                                                                                                                           

func Encrypt(key, text string) string {                                                                                                                                                                     
    block, err := aes.NewCipher([]byte(key))                                                                                                                                                                
    if err != nil { panic(err) }                                                                                                                                                                            
    plaintext := []byte(text)                                                                                                                                                                               
    cfb := cipher.NewCFBEncrypter(block, iv)                                                                                                                                                                
    ciphertext := make([]byte, len(plaintext))                                                                                                                                                              
    cfb.XORKeyStream(ciphertext, plaintext)                                                                                                                                                                 
    return encodeBase64(ciphertext)                                                                                                                                                                         
}                                                                                                                                                                                                           

func Decrypt(key, text string) string {                                                                                                                                                                     
    block, err := aes.NewCipher([]byte(key))                                                                                                                                                                
    if err != nil { panic(err) }                                                                                                                                                                            
    ciphertext := decodeBase64(text)                                                                                                                                                                        
    cfb := cipher.NewCFBEncrypter(block, iv)                                                                                                                                                                
    plaintext := make([]byte, len(ciphertext))                                                                                                                                                              
    cfb.XORKeyStream(plaintext, ciphertext)                                                                                                                                                                 
}                          
据我所知,可能需要填充字符串,但对于流密码来说,我需要填充似乎很奇怪。
以下是此错误的示例:http://play.golang.org/p/4FQBAeHgRs

2
不确定这是否有帮助,但在您发布的游乐场示例中,任何长度超过16个字节的内容都会出现错误。将密钥设置为32个字节的字符串(而不是24个)可以成功解码您的“plaintext1”字符串。 - Intermernet
4
我希望IV仅为举例用常数。 IV代表初始化向量,每次加密应该随机生成,并保存在密文内部。解密时,首先从密文中提取IV,然后进行通常的解密操作。根据维基百科(http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Initialization_vector_.28IV.29),对于CBC和CFB模式,重复使用IV会泄漏第一块明文和两个消息共享的任何公共前缀的一些信息。 - Kluyg
2
很好的观点。我已经修改了我的答案,使用推荐的 IV 创建方法。 - Intermernet
免责声明:我不是安全专家。但我开发了这个库,可能会对你有所帮助 https://github.com/phylake/go-crypto ,并且我强烈推荐《密码工程:设计原理与实际应用》(http://www.amazon.com/Cryptography-Engineering-Principles-Practical-Applications/dp/0470474246),它提供了很多关于各种流和块密码模式的清晰度。 - Brandon Cook
6个回答

65

这基于NewCFBEncrypter / NewCFBDecrypter示例,并且似乎可以满足您的需求:

编辑:根据Kluyg的评论有关IV创建的建议,我修改了示例代码以使用从密文中创建IV的同一方法,与链接的示例相同。 (在生产代码中,IV应该每次单独生成。感谢RoundSparrow hilltx指出这一点。)

我认为您遇到的问题是由于无效的密钥长度,但我不确定。

package main

import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "encoding/base64"
    "errors"
    "fmt"
    "io"
    "log"
)

func main() {
    key := []byte("a very very very very secret key") // 32 bytes
    plaintext := []byte("some really really really long plaintext")
    fmt.Printf("%s\n", plaintext)
    ciphertext, err := encrypt(key, plaintext)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%0x\n", ciphertext)
    result, err := decrypt(key, ciphertext)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%s\n", result)
}

// See alternate IV creation from ciphertext below
//var iv = []byte{35, 46, 57, 24, 85, 35, 24, 74, 87, 35, 88, 98, 66, 32, 14, 05}

func encrypt(key, text []byte) ([]byte, error) {
    block, err := aes.NewCipher(key)
    if err != nil {
        return nil, err
    }
    b := base64.StdEncoding.EncodeToString(text)
    ciphertext := make([]byte, aes.BlockSize+len(b))
    iv := ciphertext[:aes.BlockSize]
    if _, err := io.ReadFull(rand.Reader, iv); err != nil {
        return nil, err
    }
    cfb := cipher.NewCFBEncrypter(block, iv)
    cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(b))
    return ciphertext, nil
}

func decrypt(key, text []byte) ([]byte, error) {
    block, err := aes.NewCipher(key)
    if err != nil {
        return nil, err
    }
    if len(text) < aes.BlockSize {
        return nil, errors.New("ciphertext too short")
    }
    iv := text[:aes.BlockSize]
    text = text[aes.BlockSize:]
    cfb := cipher.NewCFBDecrypter(block, iv)
    cfb.XORKeyStream(text, text)
    data, err := base64.StdEncoding.DecodeString(string(text))
    if err != nil {
        return nil, err
    }
    return data, nil
}

输出:

一些非常非常非常长的纯文本
54618bd6bb10612a7b590c53192df214501e01b685540b012581a0ed9ff3ddaa1f4177cc6186b501fb8cce0c2eb764daff475aab724d4d33e614d7d89cf556d8512fd920018c090f
一些非常非常非常长的纯文本

Playground


1
非常感谢您提供如此强大的答案。我已经将您的示例调整为适用于我的程序。看起来似乎是密钥长度的问题,这很奇怪,因为我本来期望它会因为无效的长度而报错,并且根本不工作。 - jawr
1
我已经修改了示例代码,使用了从密文创建IV的推荐方法。我不喜欢那个措辞。IV应该与密文一起发送,通常是在前面添加,但它们应该是独立生成的。IV必须对每个密钥使用都是唯一的,因此即使加密相同的字符串“hello”,输出也将是唯一的。 - RoundSparrow hilltx
@RoundSparrowhilltx 谢谢,我已更新答案以指定示例代码使用与链接示例相同的方法。 - Intermernet
4
我想指出,在加密数据之前对其进行Base64编码毫无意义。此外,这会增加额外的处理时间并使数据的大小增加约33%。在原始问题中,加密和解密函数正在处理文本,因此加密后的数据需要在返回之前进行编码。这里定义的函数处理[]byte类型的数据,因此没有必要对数据进行Base64编码。 - PiersyP
这里没有身份验证(HMAC),因此不安全。如果您的整个明文可以放入内存(如上所示),建议使用GCM。 - Xeoncross

12

加密技术相当复杂,且Go的库或许不够高级,因此很容易出现错误。

对于任何想要向领域专家(CoreOS 的一位安全开发人员)学习正确做法的人,以下链接展示了AES加密的良好示例以及其他常见的加密用途。

https://github.com/gtank/cryptopasta


加密本身并不难,难的是正确使用它。 - Alpha2k

4

看起来你的操作顺序有点颠倒。这是你似乎正在做的事情:

ct = encrypt(encode(pt))
pt = decode(decrypt(ct))

它应该更像这样:
ct = encode(encrypt(pt))
pt = decrypt(decode(ct))

以下内容适用于我:
func Encrypt(key, text []byte) string {
    block, err := aes.NewCipher(key)
    if err != nil {
        panic(err)
    }
    ciphertext := make([]byte, aes.BlockSize+len(text))
    iv := ciphertext[:aes.BlockSize]
    if _, err := io.ReadFull(crand.Reader, iv); err != nil {
        panic(err)
    }
    cfb := cipher.NewCFBEncrypter(block, iv)
    cfb.XORKeyStream(ciphertext[aes.BlockSize:], text)
    return encodeBase64(ciphertext)
}


func Decrypt(key []byte, b64 string) string {
    text := decodeBase64(b64)
    block, err := aes.NewCipher(key)
    if err != nil {
        panic(err)
    }
    if len(text) < aes.BlockSize {
        panic("ciphertext too short")
    }
    iv := text[:aes.BlockSize]
    text = text[aes.BlockSize:]
    cfb := cipher.NewCFBDecrypter(block, iv)
    cfb.XORKeyStream(text, text)
    return string(text)
}

4

这是我刚刚完成的工作演示,它主要使用来自go文档的代码示例,但经过调整,以满足大多数应用程序(包括我的用例)对加密方法的期望。

它使用AES加密。 将字符串加密为base64字符串。易于在URL和数据库上使用。 从上面创建的base64字符串解密为原始文本。

各处都是简单的文本转换。

GIST:这是代码片段,请让我知道是否需要改进。

这是一个简单的go文件,可以直接运行。


1

许多人已经提供了很好的答案。但是,正如@PiersyP在@Intermernet的回答评论中指出的那样,没有必要对文本进行base64编码。因此,如果有人赶时间,这里就没有进行base64编码。

package main

import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "errors"
    "fmt"
    "io"
    "log"
)

func main() {
    key := []byte("a very very very very secret key") // 32 bytes
    plaintext := []byte("some really really really long plaintext")
    fmt.Printf("%s\n", plaintext)
    ciphertext, err := encrypt(key, plaintext)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%0x\n", ciphertext)
    result, err := decrypt(key, ciphertext)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%s\n", result)
}

// See alternate IV creation from ciphertext below
//var iv = []byte{35, 46, 57, 24, 85, 35, 24, 74, 87, 35, 88, 98, 66, 32, 14, 05}

func encrypt(key, text []byte) ([]byte, error) {
    block, err := aes.NewCipher(key)
    if err != nil {
        return nil, err
    }
    ciphertext := make([]byte, aes.BlockSize + len(text))
    iv := ciphertext[:aes.BlockSize]
    if _, err := io.ReadFull(rand.Reader, iv); err != nil {
        return nil, err
    }
    cfb := cipher.NewCFBEncrypter(block, iv)
    cfb.XORKeyStream(ciphertext[aes.BlockSize:], text)
    return ciphertext, nil
}

func decrypt(key, text []byte) ([]byte, error) {
    block, err := aes.NewCipher(key)
    if err != nil {
        return nil, err
    }
    if len(text) < aes.BlockSize {
        return nil, errors.New("ciphertext too short")
    }
    iv := text[:aes.BlockSize]
    text = text[aes.BlockSize:]
    cfb := cipher.NewCFBDecrypter(block, iv)
    cfb.XORKeyStream(text, text)
    return text, nil
}

0

并非实际回答问题。但我在这里放了一个完整的工作示例,供从搜索引擎来的人使用。

通过添加哈希密码gtank/cryptopasta转换而来。您可以使用任何密码,无需担心其大小。

Go Playground

package main

import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "crypto/sha256"
    "encoding/base64"
    "errors"
    "fmt"
    "io"
)

func main() {
    key := []byte("secret")
    ct, err := Encrypt([]byte("Plain text"), key)
    if err != nil {
        panic(err)
    }

    fmt.Println("Encrypted:", base64.StdEncoding.EncodeToString(ct))

    pt, err := Decrypt(ct, key)
    if err != nil {
        panic(err)
    }
    fmt.Println("Decrypted:", string(pt))
}

// Encrypt encrypts data using 256-bit AES-GCM.  This both hides the content of
// the data and provides a check that it hasn't been altered. Output takes the
// form nonce|ciphertext|tag where '|' indicates concatenation.
func Encrypt(plaintext []byte, key []byte) (ciphertext []byte, err error) {
    k := sha256.Sum256(key)
    block, err := aes.NewCipher(k[:])
    if err != nil {
        return nil, err
    }

    gcm, err := cipher.NewGCM(block)
    if err != nil {
        return nil, err
    }

    nonce := make([]byte, gcm.NonceSize())
    _, err = io.ReadFull(rand.Reader, nonce)
    if err != nil {
        return nil, err
    }

    return gcm.Seal(nonce, nonce, plaintext, nil), nil
}

// Decrypt decrypts data using 256-bit AES-GCM.  This both hides the content of
// the data and provides a check that it hasn't been altered. Expects input
// form nonce|ciphertext|tag where '|' indicates concatenation.
func Decrypt(ciphertext []byte, key []byte) (plaintext []byte, err error) {
    k := sha256.Sum256(key)
    block, err := aes.NewCipher(k[:])
    if err != nil {
        return nil, err
    }

    gcm, err := cipher.NewGCM(block)
    if err != nil {
        return nil, err
    }

    if len(ciphertext) < gcm.NonceSize() {
        return nil, errors.New("malformed ciphertext")
    }

    return gcm.Open(nil,
        ciphertext[:gcm.NonceSize()],
        ciphertext[gcm.NonceSize():],
        nil,
    )
}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接