【问题标题】:Go RSA decrypt using public key implementation from java使用来自 java 的公钥实现 Go RSA 解密
【发布时间】:2021-10-29 05:45:01
【问题描述】:

提供者有这个示例 JAVA 代码来使用公钥解密 RSA。

 public static byte[] decryptByPublicKey(byte[] encryptedData, String publicKey)
            throws Exception {
        byte[] keyBytes = Base64.decodeBase64(publicKey);
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        Key publicK = keyFactory.generatePublic(x509KeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        
        cipher.init(Cipher.DECRYPT_MODE, publicK);
        int inputLen = encryptedData.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        while (inputLen - offSet > 0) {
            if (inputLen - offSet >(2048/8)) {
                cache = cipher.doFinal(encryptedData, offSet,(2048/8));
            } else {
                cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i *(2048/8);
        }
        byte[] decryptedData = out.toByteArray();
        out.close();
        return decryptedData;
    }

我尝试在 Go 中编写等效的代码,但没有任何运气。

base64DecodeBytesKey, err := base64.StdEncoding.DecodeString(os.Getenv("PUBKEY"))
    if err != nil {
        Log(logRef, " error reading  pubkey", err)
    }

    pubKey, err := x509.ParsePKCS1PrivateKey(base64DecodeBytesKey)
c := new(big.Int)
    m := new(big.Int)
    m.SetBytes(data)
    e := big.NewInt(int64(pubKey.E))
    c.Exp(m, e, pubKey.N)
    out := c.Bytes()
    skip := 0
    Log(" payload size:--> ", len(data))
    for i := 2; i < len(out); i++ {
        if i+1 >= len(out) {
            break
        }
        if out[i] == 0xff && out[i+1] == 0 {
            skip = i + 2
            break
        }
    }
    return out[skip:]

以上是第一次失败的尝试。 rsa.DecryptPKCS1v15 确实需要私钥,但提供商坚持认为这不是必需的。

encryptedBlockBytes, err := rsa.DecryptPKCS1v15(
            rand.Reader,
            NO_PRIVATE_KEY_PROVIDED,
            payloadBytes[start:finish])
    

有没有办法使用 Go crypt 库从 RSA 验证 PSS 中获取解密的有效负载?

【问题讨论】:

  • 它没有按预期工作。您需要私钥来解密由公钥加密的信息。这就是RSAasymmetric encryption 实际工作和发明的方式。与 Go 语言无关
  • @VictorGubin 我明白了。但据说他们的 java 示例可以工作。
  • 如果有人调用像String publicKey 这样的函数参数,这并不意味着它是一个真正的公钥:) 根据代码 - 是私有的。如果您可以访问原始代码库,请跟进代码并查看此参数的来源,我想它是一些应用程序资源。
  • @VictorGubin:在java代码中publicKey确实是一个公钥,从代码中可以看出。
  • 从 Java 代码来看,填充并不清楚,因为只指定了算法,但没有指定填充。在这种情况下,填充取决于提供者,例如PKCS#1 v1.5 用于 SunJCE 提供程序。

标签: java go rsa crypt


【解决方案1】:

从 Java 代码来看,填充并不清楚,因为只指定了算法,但没有指定填充。在这种情况下,填充取决于提供者,例如PKCS#1 v1.5 用于 SunJCE 提供程序。

假设 PKCS#1 v1.5,Cipher.ENCRYPT_MODE/private key 和 Cipher.DECRYPT_MODE/public key 组合应用 RSASSA-PKCS1-v1_5 作为填充。这在功能上与使用NonewithRSA 进行签名/验证相同(除了使用NonewithRSA 验证与使用公钥解密 相比额外检查数据的相等性)。

NonewithRSA 表示数据没有经过哈希处理,也没有添加摘要 ID。这个算法实际上是为了签署已经散列的数据(在摘要 ID 被添加之后)。它绝不意味着对未散列的数据进行签名,即 Java 代码滥用此算法。
由于 RSA 限制了消息大小(密钥大小减去填充所需的空间),因此只能对适当的短消息进行签名。大小超过允许大小的未散列数据因此会强制执行多个签名。这就是Java代码中多次解密的原因。
使用散列数据不仅出于实际原因(签署长消息)有用,而且出于安全原因也是必要的,s。例如herehere

要在 Go 中实现 Java 代码的功能,需要像在 Java 代码中一样在 Go 中进行签名/验证的低级实现。
另一种可能性是实现解密 (m = c^e) 并自己删除填充,对于 RSASSA-PKCS1-v1_5,填充仅由左侧 0x0001 和右侧 0x00 框住的 0xFF 值序列组成。

在下文中,我考虑第二种方法。下面的 Go 代码执行以下操作:

  • 导入公钥
  • Base64 解码密文
  • 将密文分割成单独的签名块(这里 3 个块包含相同的数据:快速棕色狐狸跳过懒狗
  • 解密每个签名块 (m = c ^ e)
  • 连接解密的签名块
package main

import (
    "fmt"
    "math/big"
    "encoding/pem"
    "crypto/x509"
    "crypto/rsa"
    "encoding/base64"
    "bytes"
    "io"
)

func main() {

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

    // Import public key
    pubKey := ImportSPKIPublicKeyPEM(pubKeyPem);    
    // Base64 decode ciphertext
    ciphertextBytes, _ := base64.StdEncoding.DecodeString("ajQbkszbZ97YZaPSRBab9vj0DDLm9tTrQwSZ+ucPj+cYSmw06KLCtRH3SPn3b2DqSd1revLXqxMtSzFmjRvZ5F8y3nzdP8NJaRplOigbPFhKZTv7xBVK5ATEmLukgtI7f+d3KdmGUG+cyTkfxIrMBvB3BIS5oTiMNmC9pqLaWcDVF9qpuxnwEMQJbeO9nTklpdv+F8BrchHmeUkKRrMJBoPbbcfq9Hi4bHiFyxPWhwB66d/AryCKsFRhaX6hSkTL+0NvuhVhv98wdo3juv2Il50XKOCbfc8kUG628TcSK6n31piLF9cntSVTB/L/pVfcAxEwx4hcUhLuqmk6EZIJvGo0G5LM22fe2GWj0kQWm/b49Awy5vbU60MEmfrnD4/nGEpsNOiiwrUR90j5929g6knda3ry16sTLUsxZo0b2eRfMt583T/DSWkaZTooGzxYSmU7+8QVSuQExJi7pILSO3/ndynZhlBvnMk5H8SKzAbwdwSEuaE4jDZgvaai2lnA1RfaqbsZ8BDECW3jvZ05JaXb/hfAa3IR5nlJCkazCQaD223H6vR4uGx4hcsT1ocAeunfwK8girBUYWl+oUpEy/tDb7oVYb/fMHaN47r9iJedFyjgm33PJFButvE3Eiup99aYixfXJ7UlUwfy/6VX3AMRMMeIXFIS7qppOhGSCbxqNBuSzNtn3thlo9JEFpv2+PQMMub21OtDBJn65w+P5xhKbDToosK1EfdI+fdvYOpJ3Wt68terEy1LMWaNG9nkXzLefN0/w0lpGmU6KBs8WEplO/vEFUrkBMSYu6SC0jt/53cp2YZQb5zJOR/EiswG8HcEhLmhOIw2YL2motpZwNUX2qm7GfAQxAlt472dOSWl2/4XwGtyEeZ5SQpGswkGg9ttx+r0eLhseIXLE9aHAHrp38CvIIqwVGFpfqFKRMv7Q2+6FWG/3zB2jeO6/YiXnRco4Jt9zyRQbrbxNxIrqffWmIsX1ye1JVMH8v+lV9wDETDHiFxSEu6qaToRkgm8")
    // Split ciphertext into signature chunks a 2048/8 bytes and decrypt each chunk
    reader := bytes.NewReader(ciphertextBytes)
    var writer bytes.Buffer
    ciphertextBytesChunk := make([]byte, 2048/8)
    for { 
        n, _ := io.ReadFull(reader, ciphertextBytesChunk)
        if (n == 0) { 
            break
        }
        decryptChunk(ciphertextBytesChunk, &writer, pubKey)
    }
    // Concatenate decrypted signature chunks
    decryptedData := writer.String()
    fmt.Println(decryptedData)      
}

func ImportSPKIPublicKeyPEM(spkiPEM string) (*rsa.PublicKey) {
    body, _ := pem.Decode([]byte(spkiPEM )) 
    publicKey, _ := x509.ParsePKIXPublicKey(body.Bytes)
    if publicKey, ok := publicKey.(*rsa.PublicKey); ok {
        return publicKey
    } else {
        return nil
    }   
}

func decryptChunk(ciphertextBytesChunk []byte , writer *bytes.Buffer, pubKey *rsa.PublicKey ){
    // Decrypt each signature chunk
    ciphertextInt := new(big.Int)
    ciphertextInt.SetBytes(ciphertextBytesChunk)
    decryptedPaddedInt := decrypt(new(big.Int), pubKey, ciphertextInt)  
    // Remove padding
    decryptedPaddedBytes := make([]byte, pubKey.Size())
    decryptedPaddedInt.FillBytes(decryptedPaddedBytes)
    start := bytes.Index(decryptedPaddedBytes[1:], []byte{0}) + 1 // // 0001FF...FF00<data>: Find index after 2nd 0x00
    decryptedBytes := decryptedPaddedBytes[start:]  
    // Write decrypted signature chunk
    writer.Write(decryptedBytes)
}

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

输出:

The quick brown fox jumps over the lazy dogThe quick brown fox jumps over the lazy dogThe quick brown fox jumps over the lazy dog

请注意,代码只是一个示例实现,尤其不包括异常处理。


测试:
下面的Java代码

String publicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoZ67dtUTLxoXnNEzRBFBmwukEJGC+y69cGgpNbtElQj3m4Aft/7cu9qYbTNguTSnCDt7uovZNb21u1vpZwKHyVgFEGO4SA8RNnjhJt2D7z8RDMWX3saody7jo9TKlrPABLZGo2o8vadW8Dly/v+Id0YDheCkVCoCEeUjQ8koXZhTwhYkGPu+vkdiqX5cUaiVTu1uzt591aO5Vw/hV4DIhFKnOTnYXnpXiwRwtPyYoGTa64yWfi2t0bv99qz0BgDjQjD0civCe8LRXGGhyB1U1aHjDDGEnulTYJyEqCzNGwBpzEHUjqIOXElFjt55AFGpCHAuyuoXoP3gQvoSj6RCsQIDAQAB"; 
byte[] ciphertext = Base64.getDecoder().decode("ajQbkszbZ97YZaPSRBab9vj0DDLm9tTrQwSZ+ucPj+cYSmw06KLCtRH3SPn3b2DqSd1revLXqxMtSzFmjRvZ5F8y3nzdP8NJaRplOigbPFhKZTv7xBVK5ATEmLukgtI7f+d3KdmGUG+cyTkfxIrMBvB3BIS5oTiMNmC9pqLaWcDVF9qpuxnwEMQJbeO9nTklpdv+F8BrchHmeUkKRrMJBoPbbcfq9Hi4bHiFyxPWhwB66d/AryCKsFRhaX6hSkTL+0NvuhVhv98wdo3juv2Il50XKOCbfc8kUG628TcSK6n31piLF9cntSVTB/L/pVfcAxEwx4hcUhLuqmk6EZIJvGo0G5LM22fe2GWj0kQWm/b49Awy5vbU60MEmfrnD4/nGEpsNOiiwrUR90j5929g6knda3ry16sTLUsxZo0b2eRfMt583T/DSWkaZTooGzxYSmU7+8QVSuQExJi7pILSO3/ndynZhlBvnMk5H8SKzAbwdwSEuaE4jDZgvaai2lnA1RfaqbsZ8BDECW3jvZ05JaXb/hfAa3IR5nlJCkazCQaD223H6vR4uGx4hcsT1ocAeunfwK8girBUYWl+oUpEy/tDb7oVYb/fMHaN47r9iJedFyjgm33PJFButvE3Eiup99aYixfXJ7UlUwfy/6VX3AMRMMeIXFIS7qppOhGSCbxqNBuSzNtn3thlo9JEFpv2+PQMMub21OtDBJn65w+P5xhKbDToosK1EfdI+fdvYOpJ3Wt68terEy1LMWaNG9nkXzLefN0/w0lpGmU6KBs8WEplO/vEFUrkBMSYu6SC0jt/53cp2YZQb5zJOR/EiswG8HcEhLmhOIw2YL2motpZwNUX2qm7GfAQxAlt472dOSWl2/4XwGtyEeZ5SQpGswkGg9ttx+r0eLhseIXLE9aHAHrp38CvIIqwVGFpfqFKRMv7Q2+6FWG/3zB2jeO6/YiXnRco4Jt9zyRQbrbxNxIrqffWmIsX1ye1JVMH8v+lV9wDETDHiFxSEu6qaToRkgm8");
byte[] decrypted = decryptByPublicKey(ciphertext, publicKey); 
System.out.println(new String(decrypted, StandardCharsets.UTF_8));

使用您发布的方法给出相同的结果。

【讨论】: