Skip to content

proposal: crypto/rsa: support RSA/ECB/OAEPWithSHA-256AndMGF1Padding #72781

@javen-yan

Description

@javen-yan

Proposal Details

Hello, everyone.

Currently, I've encountered an issue where the RSA encryption algorithms are inconsistent across different languages during the project integration process. To address this problem, I had no choice but to modify some code in the crypto basic library.

Process:

  1. When integrating with a Java implementation of the RSA encryption program, it can be found that the Java implementation uses the RSA/ECB/OAEPWithSHA-256AndMGF1Padding method.
  2. When I used the private key for decryption, I found that the DecryptOAEP method in crypto only provides a single input parameter for the Hash algorithm. When I used sha256.New() to create the hash, the lack of MGF1Padding caused the decryption to fail.
    Code:
func DecryptOAEP(hash hash.Hash, random io.Reader, priv *PrivateKey, ciphertext []byte, label []byte) ([]byte, error) { // hash, hash reuse common Hash	return decryptOAEP(hash, hash, random, priv, ciphertext, label) } 
  1. Later, I discovered that PrivateKey provides a Decrypt method, which can be implemented through the extensible opts crypto.DecrypterOpts, thus solving my problem.
func (priv *PrivateKey) Decrypt(rand io.Reader, ciphertext []byte, opts crypto.DecrypterOpts) (plaintext []byte, err error) { if opts == nil { return DecryptPKCS1v15(rand, priv, ciphertext)	} switch opts := opts.(type) { case *OAEPOptions: if opts.MGFHash == 0 { return decryptOAEP(opts.Hash.New(), opts.Hash.New(), rand, priv, ciphertext, opts.Label)	} else { return decryptOAEP(opts.Hash.New(), opts.MGFHash.New(), rand, priv, ciphertext, opts.Label)	} case *PKCS1v15DecryptOptions: if l := opts.SessionKeyLen; l > 0 { plaintext = make([]byte, l) if _, err := io.ReadFull(rand, plaintext); err != nil { return nil, err	} if err := DecryptPKCS1v15SessionKey(rand, priv, ciphertext, plaintext); err != nil { return nil, err	} return plaintext, nil	} else { return DecryptPKCS1v15(rand, priv, ciphertext)	} default: return nil, errors.New("crypto/rsa: invalid options for Decrypt")	} }
  1. Originally, I thought I was just one step away from success. However, here comes the problem when I implemented the Golang encryption. During decryption, we used two hash algorithms as factors for decryption to implement RSA/ECB/OAEPWithSHA-256AndMGF1Padding, and the official also provides standard APIs.
  2. During encryption, there should be a similar interface to support RSA/ECB/OAEPWithSHA-256AndMGF1Padding. Unfortunately, I couldn't find such a method.

Optimization suggestion:

Can you geniuses provide an external interface that exposes a method similar to func (priv *PrivateKey) Decrypt(rand io.Reader, ciphertext []byte, opts crypto.DecrypterOpts) (plaintext []byte, err error)?

The following is the code I used to solve my problem. I hope it can be of some help. Of course, there is still much room for improvement.

type EncrypterOpts any func (pub *PublicKey) Encrypt(rand io.Reader, msg []byte, opts EncrypterOpts) (plaintext []byte, err error) { if opts == nil { return EncryptPKCS1v15(rand, pub, msg)	} switch opts := opts.(type) { case *OAEPOptions: if opts.MGFHash == 0 { return encryptOAEP(opts.Hash.New(), opts.Hash.New(), rand, pub, msg, opts.Label)	} else { return encryptOAEP(opts.Hash.New(), opts.MGFHash.New(), rand, pub, msg, opts.Label)	} default: return nil, errors.New("crypto/rsa: invalid options for Encrypt")	} } func encryptOAEP(hash, MGFHash hash.Hash, random io.Reader, pub *PublicKey, msg []byte, label []byte) ([]byte, error) { // Note that while we don't commit to deterministic execution with respect // to the random stream, we also don't apply MaybeReadByte, so per Hyrum's // Law it's probably relied upon by some. It's a tolerable promise because a // well-specified number of random bytes is included in the ciphertext, in a // well-specified way. if err := checkPub(pub); err != nil { return nil, err	} hash.Reset() k := pub.Size() if len(msg) > k-2*hash.Size()-2 { return nil, 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	} mgf1XOR(db, MGFHash, seed) mgf1XOR(seed, MGFHash, db) return encrypt(pub, em) }

I'm looking forward to your replies.

Metadata

Metadata

Assignees

No one assigned

    Labels

    LibraryProposalIssues describing a requested change to the Go standard library or x/ libraries, but not to a toolProposalProposal-CryptoProposal related to crypto packages or other security issues

    Type

    No type

    Projects

    Status

    Incoming

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions