dongmou3615 2018-06-08 05:42
浏览 336
已采纳

AES-256-CBC加密在golang和node / php之间不匹配

I've been having a bit of problems trying to figure out why my encryption is different in go compared to php and node. I was hoping someone could help me figure out the differences. Let's assume this is the data:

plaintext: hello big worldshello big worlds

key: jJr44P3WSM5F8AC573racFpzU5zj7Rg5

iv: 97iEhhtgVjoVwdUw

Here are the resulting encryptions in base64:

Node and PHP return :

OTdpRWhodGdWam9Wd2RVd0OgJ+Z7pSCVioYq41721jarxqLKXN3PcnnY6/AOrHeEfsTxXfCgm2uUi+vmCAdpvw==

Go returns:

OTdpRWhodGdWam9Wd2RVd0OgJ+Z7pSCVioYq41721jarxqLKXN3PcnnY6/AOrHeE

As you can see they're almost identical and its been driving me crazy. Could you guys take quick look at the encryption code below and give me hints on what the problem could be?

GO:

func EncryptString(plainstring string, keystring string, encFormat int, ivOverride bool) (string) {
    // Load your secret key from a safe place and reuse it across multiple
    // NewCipher calls. (Obviously don't use this example key for anything
    // real.) If you want to convert a passphrase to a key, use a suitable
    // package like bcrypt or scrypt.
    key := []byte(keystring)
    plaintext := []byte(plainstring)

    // CBC mode works on blocks so plaintexts may need to be padded to the
    // next whole block. For an example of such padding, see
    // https://tools.ietf.org/html/rfc5246#section-6.2.3.2. Here we'll
    // assume that the plaintext is already of the correct length.
    if len(plaintext)%aes.BlockSize != 0 {
        panic("plaintext is not a multiple of the block size")
    }

    block, err := aes.NewCipher(key)
    if err != nil {
        panic(err)
    }

    // The IV needs to be unique, but not secure. Therefore it's common to
    // include it at the beginning of the ciphertext.
    ciphertext := make([]byte, aes.BlockSize+len(plaintext))
    iv := ciphertext[:aes.BlockSize]
    if _, err := io.ReadFull(bytes.NewReader([]byte("97iEhhtgVjoVwdUw")), iv); err != nil {
       panic(err)
    }

    mode := cipher.NewCBCEncrypter(block, iv)
    mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)

    // It's important to remember that ciphertexts must be authenticated
    // (i.e. by using crypto/hmac) as well as being encrypted in order to
    // be secure.

    return base64.StdEncoding.EncodeToString(ciphertext)
}

NODE:

encryptString: function(string, key, fmt = null, ivOverride = false) {
    // Build an initialisation vector
    let iv;
    if(!ivOverride) {
        iv = crypto.randomBytes(IV_NUM_BYTES).toString('hex').slice(0,16);
    } else {
        iv = IV_OVERRIDE_VALUE; //97iEhhtgVjoVwdUw
    }
    // and encrypt
    let encryptor = crypto.createCipheriv('aes-256-cbc', key, iv);
    let encryptedData = encryptor.update(string, 'utf8', 'binary') + encryptor.final('binary');
    encryptedData = iv+''+encryptedData;
    encryptedData = Buffer.from(encryptedData, 'binary').toString('base64');

    return encryptedData;
}

I've noticed that removing encryptor.final('binary') makes the two result in the same encryption but php does not have the .final() thing going for it. Php uses open_ssl_encrypt() which seems to have this built in. Is there a way to add an equivalent in go? Looking for advice. Thanks

展开全部

  • 写回答

1条回答 默认 最新

  • dongyan9950 2018-06-08 06:12
    关注

    Okay, I managed to get the go output to match your other outputs, but I'm not 100% clear on all of the details--in particular why the PHP and Node versions behave the way they do (your output from the Go version seems like the "correct" result in my mind, as I'll explain).

    My first observation was that your output from Node and PHP was longer than the Go version, by approximately one block length, and only the tail end is different. This told me that, somehow, those versions are being padded more than the go version.

    So, I tried padding the Go version according to the default padding scheme used by PHP and Node, PKCS#7. Basically, if you need to pad by 5 bytes, then each of the padding bytes should be equal to 0x05, 6 bytes are padded with 0x06, etc. Go's default aes.BlockSize is equal to 16, so I tried padding your input string with 16 0x10 bytes. This led to the correct answer!

    Honestly, not padding the input at all if it's already block-aligned is understandable behavior, but apparently Node and PHP follow RFC 5652 and always add padding (see the edit), even if they need to add another entire block just of padding.

    Here's the Go code to make your outputs match:

    package main
    
    import (
        "bytes"
        "crypto/aes"
        "crypto/cipher"
        "encoding/base64"
        "fmt"
        "io"
    )
    
    // Based on Wikipedia: https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7
    func PadToBlockSize(input string) string {
        paddingNeeded := aes.BlockSize - (len(input) % aes.BlockSize)
        if paddingNeeded >= 256 {
            panic("I'm too lazy to handle this case for the sake of an example :)")
        }
    
        if paddingNeeded == 0 {
            paddingNeeded = aes.BlockSize
        }
    
        // Inefficient, once again, this is an example only!
        for i := 0; i < paddingNeeded; i++ {
            input += string(byte(paddingNeeded))
        }
        return input
    }
    
    // (Identical to your code, I just deleted comments to save space)
    func EncryptString(plainstring string, keystring string, encFormat int, ivOverride bool) string {
        key := []byte(keystring)
        plaintext := []byte(plainstring)
        if len(plaintext)%aes.BlockSize != 0 {
            panic("plaintext is not a multiple of the block size")
        }
        block, err := aes.NewCipher(key)
        if err != nil {
            panic(err)
        }
        ciphertext := make([]byte, aes.BlockSize+len(plaintext))
        iv := ciphertext[:aes.BlockSize]
        if _, err := io.ReadFull(bytes.NewReader([]byte("97iEhhtgVjoVwdUw")), iv); err != nil {
            panic(err)
        }
        mode := cipher.NewCBCEncrypter(block, iv)
        mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)
        return base64.StdEncoding.EncodeToString(ciphertext)
    }
    
    func main() {
        plaintext := "hello big worldshello big worlds"
        key := "jJr44P3WSM5F8AC573racFpzU5zj7Rg5"
        phpText := "OTdpRWhodGdWam9Wd2RVd0OgJ+Z7pSCVioYq41721jarxqLKXN3PcnnY6/AOrHeEfsTxXfCgm2uUi+vmCAdpvw=="
    
        fmt.Println("Go : " + EncryptString(PadToBlockSize(plaintext), key, 0, false))
        fmt.Println("PHP: " + phpText)
    }
    

    Edit:

    Actually, it looks like Node and PHP are just following RFC 5652 correctly, which mandates that all input needs to be padded. The fact that padding will always be present, even if the input was block-aligned, disambiguates decryption. Go just leaves the padding step to the user.

    展开全部

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
编辑
预览

报告相同问题?

悬赏问题

  • ¥15 PADS Logic 原理图
  • ¥15 PADS Logic 图标
  • ¥15 电脑和power bi环境都是英文如何将日期层次结构转换成英文
  • ¥20 气象站点数据求取中~
  • ¥15 如何获取APP内弹出的网址链接
  • ¥15 wifi 图标不见了 不知道怎么办 上不了网 变成小地球了
手机看
程序员都在用的中文IT技术交流社区

程序员都在用的中文IT技术交流社区

专业的中文 IT 技术社区,与千万技术人共成长

专业的中文 IT 技术社区,与千万技术人共成长

关注【CSDN】视频号,行业资讯、技术分享精彩不断,直播好礼送不停!

关注【CSDN】视频号,行业资讯、技术分享精彩不断,直播好礼送不停!

客服 返回
顶部