Java RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING 迁移到 Go

3

我想从Java迁移到Go的代码,这些方法应该能够互相加密/解密对方的输出,但它们生成不同的结果并且不能解密其他人的密文:

Java代码

public static byte[] encrypt(byte[] data, PublicKey publicKeyObject)
        throws BadPaddingException, IllegalBlockSizeException,
        InvalidKeyException, NoSuchPaddingException,
        NoSuchAlgorithmException {
    Cipher cipher = Cipher
            .getInstance("RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING");

    OAEPParameterSpec oaepParameterSpec = new OAEPParameterSpec("SHA-256",
            "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT);
    try {
        cipher.init(Cipher.ENCRYPT_MODE, publicKeyObject,
                oaepParameterSpec);
    } catch (InvalidAlgorithmParameterException e) {
        e.printStackTrace();
        return null;
    }
    return cipher.doFinal(data);
}

private static byte[] decrypt(byte[] data, PrivateKey privateKeyObj)
        throws NoSuchPaddingException, NoSuchAlgorithmException,
        InvalidKeyException, BadPaddingException,
        IllegalBlockSizeException {
    Cipher cipher = Cipher
            .getInstance("RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING");

    OAEPParameterSpec oaepParameterSpec = new OAEPParameterSpec("SHA-256",
            "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT);
    try {
        cipher.init(Cipher.DECRYPT_MODE, privateKeyObj, oaepParameterSpec);

    } catch (InvalidAlgorithmParameterException e) {
        e.printStackTrace();
        return null;
    }

    return cipher.doFinal(data);
}

Go 语言代码

rng := rand.Reader

ciphertext, err := rsa.EncryptOAEP(sha256.New(), rng, rsaPublicKey, secretMessage, label)
if err != nil {
    fmt.Printf("Error from encryption: %s\n", err)
    return
}
clearText, err := rsa.DecryptOAEP(sha256.New(), rng, rsaPrivateKey, ciphertext, label)
if err != nil {
    fmt.Printf("Error from decryption: %s\n", err)
    return
}

我甚至尝试将 sha1 作为 Go 的第一个参数,但结果不同。

有人能帮我吗?


Go 代码中的 label 包含什么数据? - Topaco
2
假设Go代码使用SHA256作为OAEP和MGF1摘要,而Java代码则应用SHA256作为OAEP摘要和SHA-1作为MGF1摘要。 - Topaco
1
RSA加密几乎总是随机的;即使使用相同的密钥和代码运行,也会生成不同的密文。这本身并不是任何类型的指标。默认的Java提供程序有一个讨厌的习惯,就是将MGF1哈希保留为SHA-1。 - Maarten Bodewes
@Topaco,谢谢你,你的解决方案有效,我已经在GitHub上提交了一个请求给Go开发团队,请问你能否也提供加密方法? - hmmftg
@hmmftg - 请查看我的回答。 - Topaco
2个回答

6

为什么两个代码不兼容的原因已经在评论和其他答案中解释过:与Java代码不同,Go的crypto/rsa包不允许单独指定OAEP摘要和MGF1摘要,导致两个代码使用了不同的MGF1摘要。
相比之下,这个答案应该关注如何适应crypto/rsa包来解决这个问题。

两个摘要的含义在RFC 8017中描述,更精确地说,在7.1 RSAES-OAEP部分定义了OAEP。
作为选项和输入参数,指定了一个摘要(OAEP摘要),一个掩码生成函数,一个标签,消息和公钥,参见7.1.1加密操作。 OAEP摘要用于哈希标签,参见7.1.1, 步骤2a。RFC 8017将MGF1 专门(参见B.2.1 MGF1)定义为掩码生成函数,因此通常在OAEP中使用。 MGF1基于一个摘要(MGF1摘要)。
RFC 8017指定了以下默认值,参见A.2.1 RSAES-OAEP:MGF1,SHA1用于OAEP和MGF1摘要,以及空标签。

虽然SHA-1现在被认为是不安全的,但在OAEP上下文中没有已知的不安全性,详情请参见这里。然而,作为一项预防措施或在从生态系统中消除SHA-1的过程中,现在经常使用SHA256。
此外,RFC 8017不排除在OAEP和MGF1摘要中使用不同的摘要,就像您的示例一样。
因此,实现应允许独立指定两个摘要,但是crypto/rsa包未能做到这一点。
为了允许分别指定两个摘要,必须在EncryptOAEP()DecryptOAEP()函数中使用第二个参数来传递MGF1摘要,然后应用于MGF1:
func EncryptOAEP(hash hash.Hash, hashMGF1 hash.Hash, random io.Reader, pub *rsa.PublicKey, msg []byte, label []byte) ([]byte, error) {
    ...
    hashMGF1.Reset()
    mgf1XOR(seed, hashMGF1, db) 
    mgf1XOR(db, hashMGF1, seed) 
    ...
}

类似地,DecryptOAEP()也是如此。

最好的实现方式是相应地调整crypto/rsa包本身。或者,作为解决方法,可以从crypto/rsa包中复制所需的函数并进行修改,就像下面的代码和测试一样:

package main

import (
    "crypto/rand"
    "crypto/subtle"
    "crypto/rsa"
    "crypto/sha256"
    "crypto/sha1"
    "crypto/x509"
    "encoding/pem"
    "encoding/base64"
    "hash"
    "errors"
    "io"
    "math/big"
    "sync"
    "fmt"
    )

func main() {

    var publicKeyData = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoZ67dtUTLxoXnNEzRBFB
mwukEJGC+y69cGgpNbtElQj3m4Aft/7cu9qYbTNguTSnCDt7uovZNb21u1vpZwKH
yVgFEGO4SA8RNnjhJt2D7z8RDMWX3saody7jo9TKlrPABLZGo2o8vadW8Dly/v+I
d0YDheCkVCoCEeUjQ8koXZhTwhYkGPu+vkdiqX5cUaiVTu1uzt591aO5Vw/hV4DI
hFKnOTnYXnpXiwRwtPyYoGTa64yWfi2t0bv99qz0BgDjQjD0civCe8LRXGGhyB1U
1aHjDDGEnulTYJyEqCzNGwBpzEHUjqIOXElFjt55AFGpCHAuyuoXoP3gQvoSj6RC
sQIDAQAB
-----END PUBLIC KEY-----`

    var privateKeyData = `-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAoZ67dtUTLxoXnNEzRBFBmwukEJGC+y69cGgpNbtElQj3m4Af
t/7cu9qYbTNguTSnCDt7uovZNb21u1vpZwKHyVgFEGO4SA8RNnjhJt2D7z8RDMWX
3saody7jo9TKlrPABLZGo2o8vadW8Dly/v+Id0YDheCkVCoCEeUjQ8koXZhTwhYk
GPu+vkdiqX5cUaiVTu1uzt591aO5Vw/hV4DIhFKnOTnYXnpXiwRwtPyYoGTa64yW
fi2t0bv99qz0BgDjQjD0civCe8LRXGGhyB1U1aHjDDGEnulTYJyEqCzNGwBpzEHU
jqIOXElFjt55AFGpCHAuyuoXoP3gQvoSj6RCsQIDAQABAoIBAGoYl5ukuJk9Ga8a
LftLELRFaghuXXui7T0zQ4pASv9DCbiM3UWeCy1OjK1zAtXR2Kywz8JgN9DtnrVF
2uyCXr0wCPL/Y2P6cCRAKh2nYQrXbcvikpXt9311zH4qHGvdx/nP5oM0JHejuJCu
Re1btiwGTB3AoF+XzBAPSZ0gGl2FqDQ7qLqqwG9Xr+78STLdN8UOUCsKV3qdTM6N
XLeXliI0XIFQgT6XMiRGEvhJVaUTJ/3q23xza87k8jpqGsh5ArtnG6LUON26rEed
BL2ome7HNV+IOR143PXVrBMyn6qnwAas+Zt+WfCbBCP0k68oL7mzLmP6IzY4KBE9
BFEo04ECgYEA9GMgi2Xm9OqjUmihMt0oPnPcMx0DR+4mZezPVED2f3garOKcWvOV
y1N/Mn5A9L785jPjWE+ui7i5DT6AMJiWxkeEdYjXmZhpG9I3pha1yaLzBXjl+Dri
/dCXZxQq+Z7axnBxwIhDNHAeeCAau6hLfzsGgv5YAvSeg6KU7Af16dkCgYEAqUzG
jvZxfV/2qPMdNh9oUcvVbIcnIphnTP1Ma7BAD6anTnSru2EDLR66yiRtdrC9E54d
4xWeTNHsSUcaQBkAsyp7Cpewgy4vmo8GE3qUu91Jk3/1ZN6jxLyMoakyzhYTmq4s
QsTPC1daUXqpRjGYzP/8dMMzlKQ2Vncp+2BXgJkCgYEAinzJ6nSahluYpZBpGLu+
nHVnaQed3lsUI1oouyP9C4ryAtp/pAK49fmg8OoewRKhmYn54Qd2b/MD2n96gQ9X
EZFhfIFJO97kYUGlC1d/OH5AnO8/0oT8MLzNrzn8iGv+qcj6jRIqk0Kd4ZC/1Wuv
LLA0JnMfSL16PjoZjg+MyTECgYBRq47RooMnBycXY4hA9q+9XcZMP3qajsiudDbs
cC7HHg7xowjBMNB2cK+NGjuQGTxs/UbPqDsgNdh1lQ5Nw4H57FFEz94/ugUO21YE
CYs8gUigFgdMLLb2DjsNNXEjx7SXVtRVNVnnz7DrQ2/rQ7vBkO+5Z/03BGyOE5g2
AsjTaQKBgDLpbXN2p3eubQGJqv/K6f/9LBux/RWGXnZ+C1oCtGrUj+Ja8N6+cd6G
Mz9Go00GCdCUZXByx6rAZQaw7kWcI646miaplX4YtbX1d2mwbnmmz9EH4aRhzdby
9VDoPXBgf4dufgNoS3xP4NS4H5oPg0gPS0vwpWspWqplLM+N/kGj
-----END RSA PRIVATE KEY-----`
    
    secretMessage := []byte("The quick brown fox jumps over the lazy dog")
    label := []byte("")
    rng := rand.Reader

    // Encryption -------------------------------------------------------------
    
    // Load public key
    pubKeyBlock, _ := pem.Decode([]byte(publicKeyData))
    var rsaPublicKey *rsa.PublicKey
    pubInterface, parseErr := x509.ParsePKIXPublicKey(pubKeyBlock.Bytes)
    if parseErr != nil {
        fmt.Println("Load public key error")
        panic(parseErr)
    }
    rsaPublicKey = pubInterface.(*rsa.PublicKey)

    ciphertext, err := EncryptOAEP(sha256.New(), sha1.New(), rng, rsaPublicKey, secretMessage, label)
    if err != nil {
            fmt.Printf("Error from encryption: %s\n", err)
            return
    }

    // Decryption -------------------------------------------------------------
    
    // Load private key
    privateKeyBlock, _ := pem.Decode([]byte(privateKeyData))
    var rsaPrivateKey *rsa.PrivateKey
    rsaPrivateKey, _ = x509.ParsePKCS1PrivateKey(privateKeyBlock.Bytes)
    
    decrypted, err := DecryptOAEP(sha256.New(), sha1.New(), rng, rsaPrivateKey, ciphertext, label)
    if err != nil {
            fmt.Printf("Error from decryption: %s\n", err)
            return
    }
    fmt.Println("Go Encryption/Decryption : " + string(decrypted))  
    
    // Cross-platform test: ciphertext from Java
    /*
        Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING");
        OAEPParameterSpec oaepParameterSpec = new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT);
        cipher.init(Cipher.ENCRYPT_MODE, publicKeyObject, oaepParameterSpec);
        String ciphertextB64 = Base64.getEncoder().encodeToString(cipher.doFinal(data));
    */
    ciphertext,_ = base64.StdEncoding.DecodeString("cCrJasWOwVFrAQ8S+p7Cdn7OnCJn/FiCjZLzDkDISOSv15u1HcLbVAqNa7ory2AW/tsV5tNz5Y53azs6SN7dwYlu58YH7kwqkwfmvUwK8pLdPPRXGaUy8/gEbM4wkwHUuxbYm/bpoEjpmICBtWzb5VOsE1RWHnZu1G2BqGKe1+sE1XadVKQpBqNSahYdthY2Dk21i/PStO5S4eRrgW2nDdmxCs9UtV4MBU8BVYHYF0TYweA/udBoGTizSDjgmWn0RXYJruGvFMHWCRRlPnj+pcelatIfY4YKOHREYifKVkphkB7PT/JaVFyMZWzOtqzE13ZBWBwBmA/yCNLE/7krcg==")  
    decrypted, err = DecryptOAEP(sha256.New(), sha1.New(), rng, rsaPrivateKey, ciphertext, label)
    if err != nil {
            fmt.Printf("Error from decryption: %s\n", err)
            return
    }
    fmt.Println("Cross platform decryption: " + string(decrypted))  
    

}

// From rsa package - Encryption -------------------------------------------------------------

func EncryptOAEP(hash hash.Hash, hashMGF1 hash.Hash, random io.Reader, pub *rsa.PublicKey, msg []byte, label []byte) ([]byte, error) {
    if err := checkPub(pub); err != nil {
        return nil, err
    }
    hash.Reset()
    k := pub.Size()
    if len(msg) > k-2*hash.Size()-2 {
        return nil, rsa.ErrMessageTooLong
    }

    hash.Write(label)
    lHash := hash.Sum(nil)
    hash.Reset()

    em := make([]byte, k)
    seed := em[1 : 1+hash.Size()]
    db := em[1+hash.Size():]

    copy(db[0:hash.Size()], lHash)
    db[len(db)-len(msg)-1] = 1
    copy(db[len(db)-len(msg):], msg)

    _, err := io.ReadFull(random, seed)
    if err != nil {
        return nil, err
    }

    hashMGF1.Reset()
    mgf1XOR(db, hashMGF1, seed)
    mgf1XOR(seed, hashMGF1, db)

    m := new(big.Int)
    m.SetBytes(em)
    c := encrypt(new(big.Int), pub, m)

    out := make([]byte, k)
    return c.FillBytes(out), nil
}

func encrypt(c *big.Int, pub *rsa.PublicKey, m *big.Int) *big.Int {
    e := big.NewInt(int64(pub.E))
    c.Exp(m, e, pub.N)
    return c
}

// From rsa package - Decryption -------------------------------------------------------------

func DecryptOAEP(hash hash.Hash, hashMGF1 hash.Hash, random io.Reader, priv *rsa.PrivateKey, ciphertext []byte, label []byte) ([]byte, error) { // hashMGF1 hash.Hash added
    if err := checkPub(&priv.PublicKey); err != nil {
        return nil, err
    }
    k := priv.Size()
    if len(ciphertext) > k ||
        k < hash.Size()*2+2 {
        return nil, rsa.ErrDecryption
    }

    c := new(big.Int).SetBytes(ciphertext)

    m, err := decrypt(random, priv, c)
    if err != nil {
        return nil, err
    }

    hash.Write(label)
    lHash := hash.Sum(nil)
    hash.Reset()

    // We probably leak the number of leading zeros.
    // It's not clear that we can do anything about this.
    em := m.FillBytes(make([]byte, k))

    firstByteIsZero := subtle.ConstantTimeByteEq(em[0], 0)

    seed := em[1 : hash.Size()+1]
    db := em[hash.Size()+1:]

    hashMGF1.Reset()
    mgf1XOR(seed, hashMGF1, db) // apply hashMGF1
    mgf1XOR(db, hashMGF1, seed) // apply hashMGF1

    lHash2 := db[0:hash.Size()]

    // We have to validate the plaintext in constant time in order to avoid
    // attacks like: J. Manger. A Chosen Ciphertext Attack on RSA Optimal
    // Asymmetric Encryption Padding (OAEP) as Standardized in PKCS #1
    // v2.0. In J. Kilian, editor, Advances in Cryptology.
    lHash2Good := subtle.ConstantTimeCompare(lHash, lHash2)

    // The remainder of the plaintext must be zero or more 0x00, followed
    // by 0x01, followed by the message.
    //   lookingForIndex: 1 iff we are still looking for the 0x01
    //   index: the offset of the first 0x01 byte
    //   invalid: 1 iff we saw a non-zero byte before the 0x01.
    var lookingForIndex, index, invalid int
    lookingForIndex = 1
    rest := db[hash.Size():]

    for i := 0; i < len(rest); i++ {
        equals0 := subtle.ConstantTimeByteEq(rest[i], 0)
        equals1 := subtle.ConstantTimeByteEq(rest[i], 1)
        index = subtle.ConstantTimeSelect(lookingForIndex&equals1, i, index)
        lookingForIndex = subtle.ConstantTimeSelect(equals1, 0, lookingForIndex)
        invalid = subtle.ConstantTimeSelect(lookingForIndex&^equals0, 1, invalid)
    }

    if firstByteIsZero&lHash2Good&^invalid&^lookingForIndex != 1 {
        return nil, rsa.ErrDecryption
    }

    return rest[index+1:], nil
}

var bigZero = big.NewInt(0)
var bigOne = big.NewInt(1)

func decrypt(random io.Reader, priv *rsa.PrivateKey, c *big.Int) (m *big.Int, err error) {
    // TODO(agl): can we get away with reusing blinds?
    if c.Cmp(priv.N) > 0 {
        err = rsa.ErrDecryption
        return
    }
    if priv.N.Sign() == 0 {
        return nil, rsa.ErrDecryption
    }

    var ir *big.Int
    if random != nil {
        MaybeReadByte(random)

        // Blinding enabled. Blinding involves multiplying c by r^e.
        // Then the decryption operation performs (m^e * r^e)^d mod n
        // which equals mr mod n. The factor of r can then be removed
        // by multiplying by the multiplicative inverse of r.

        var r *big.Int
        ir = new(big.Int)
        for {
            r, err = rand.Int(random, priv.N)
            if err != nil {
                return
            }
            if r.Cmp(bigZero) == 0 {
                r = bigOne
            }
            ok := ir.ModInverse(r, priv.N)
            if ok != nil {
                break
            }
        }
        bigE := big.NewInt(int64(priv.E))
        rpowe := new(big.Int).Exp(r, bigE, priv.N) // N != 0
        cCopy := new(big.Int).Set(c)
        cCopy.Mul(cCopy, rpowe)
        cCopy.Mod(cCopy, priv.N)
        c = cCopy
    }

    if priv.Precomputed.Dp == nil {
        m = new(big.Int).Exp(c, priv.D, priv.N)
    } else {
        // We have the precalculated values needed for the CRT.
        m = new(big.Int).Exp(c, priv.Precomputed.Dp, priv.Primes[0])
        m2 := new(big.Int).Exp(c, priv.Precomputed.Dq, priv.Primes[1])
        m.Sub(m, m2)
        if m.Sign() < 0 {
            m.Add(m, priv.Primes[0])
        }
        m.Mul(m, priv.Precomputed.Qinv)
        m.Mod(m, priv.Primes[0])
        m.Mul(m, priv.Primes[1])
        m.Add(m, m2)

        for i, values := range priv.Precomputed.CRTValues {
            prime := priv.Primes[2+i]
            m2.Exp(c, values.Exp, prime)
            m2.Sub(m2, m)
            m2.Mul(m2, values.Coeff)
            m2.Mod(m2, prime)
            if m2.Sign() < 0 {
                m2.Add(m2, prime)
            }
            m2.Mul(m2, values.R)
            m.Add(m, m2)
        }
    }

    if ir != nil {
        // Unblind.
        m.Mul(m, ir)
        m.Mod(m, priv.N)
    }

    return
}

var (
    closedChanOnce sync.Once
    closedChan     chan struct{}
)

func MaybeReadByte(r io.Reader) { // from "crypto/internal/randutil"
    closedChanOnce.Do(func() {
        closedChan = make(chan struct{})
        close(closedChan)
    })

    select {
    case <-closedChan:
        return
    case <-closedChan:
        var buf [1]byte
        r.Read(buf[:])
    }
}

// From rsa package - both -------------------------------------------------------------

func mgf1XOR(out []byte, hash hash.Hash, seed []byte) {
    var counter [4]byte
    var digest []byte

    done := 0
    for done < len(out) {
        hash.Write(seed)
        hash.Write(counter[0:4])
        digest = hash.Sum(digest[:0])
        hash.Reset()

        for i := 0; i < len(digest) && done < len(out); i++ {
            out[done] ^= digest[i]
            done++
        }
        incCounter(&counter)
    }
}

func checkPub(pub *rsa.PublicKey) error {
    if pub.N == nil {
        return errPublicModulus
    }
    if pub.E < 2 {
        return errPublicExponentSmall
    }
    if pub.E > 1<<31-1 {
        return errPublicExponentLarge
    }
    return nil
}

var (
    errPublicModulus       = errors.New("crypto/rsa: missing public modulus")
    errPublicExponentSmall = errors.New("crypto/rsa: public exponent too small")
    errPublicExponentLarge = errors.New("crypto/rsa: public exponent too large")
)

func incCounter(c *[4]byte) {
    if c[3]++; c[3] != 0 {
        return
    }
    if c[2]++; c[2] != 0 {
        return
    }
    if c[1]++; c[1] != 0 {
        return
    }
    c[0]++
}

4

我没有设置去测试Go,但是一个常见的OAEP不兼容性是推断MGF哈希与“主”哈希相同而未指定,而不是默认为SHA-1。

换句话说,有两种哈希算法在使用中,它们可以独立选择,但是Go API可能不会给您这种灵活性。如果您选择SHA-1,它可能对两个函数都使用SHA-1,如果您选择SHA-256,则对两个函数都使用SHA-256,而您的Java代码同时使用两个函数。

作为调试策略,您可以在每个平台上编码参数并检查数据以确定参数中的任何差异。再次强调,我不熟悉Go API,但最好编码OAEP加密期间使用的参数,并将其与消息一起发送,以便接收方可以解码正确的参数而不是猜测。

使用CMS或等效标准将为您提供用于传输此信息的标准格式,以及用于接收者的关键标识符和其他重要信息。


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