Search code examples
javagorsacrypt

Go RSA decrypt using public key implementation from java


Provider has this sample JAVA code to decrypt RSA using public key.

 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;
    }

I have tried to writ the equivalent in Go without any luck.

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:]

Above was the first attempt which failed terribly. rsa.DecryptPKCS1v15 does require a Private Key which the provider insists it's not necessary.

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

Is there a way to get the decrypted payload from RSA verify PSS using Go crypt library?


Solution

  • From the Java code the padding is not clear, because only the algorithm is specified, but not the padding. In this case the padding is provider dependent, e.g. PKCS#1 v1.5 for the SunJCE provider.

    Assuming PKCS#1 v1.5, the combinations Cipher.ENCRYPT_MODE/private key and Cipher.DECRYPT_MODE/public key apply RSASSA-PKCS1-v1_5 as padding. This is functionally identical to signing/verifying with NonewithRSA (except that verifying with NonewithRSA additionally checks the equality of the data compared to decryption with the public key).

    NonewithRSA means that the data is not hashed and no digest ID is prepended. This algorithm is actually intended to sign already hashed data (after the digest ID has been prepended). It is in no way meant to sign unhashed data, i.e. the Java code misuses this algorithm.
    Since the message size is limited with RSA (key size minus the space required by padding), only appropriately short messages can be signed. Unhashed data whose size exceeds the allowed size therefore forces multiple signatures. This is the reason for the multiple decryption in the Java code.
    The use of hashed data is not only useful for practical reasons (signing long messages), but also necessary for security reasons, s. e.g. here and here.

    To implement the functionality of the Java code in Go, a low-level implementation is required for signing/verifying in Go as in the Java code.
    Another possibility would be to implement the decryption (m = c^e) and remove the padding yourself, which for RSASSA-PKCS1-v1_5 simply consists of a sequence of 0xFF values framed by 0x0001 on the left and 0x00 on the right.

    In the following, I consider the second approach. The Go code below does the following:

    • Import the public key
    • Base64 decode the ciphertext
    • Split the ciphertext into the individual signature chunks (here 3 chunks containing the same data: The quick brown fox jumps over the lazy dog)
    • Decrypt each signature chunk (m = c ^ e)
    • Concatenate the decrypted signature chunks
    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
    }
    

    with the output:

    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
    

    Please note that the code is only an example implementation and in particular does not include exception handling.


    Test:
    The Java code below

    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));
    

    with the method you posted gives the same result.