From 0a048ce5e9b599912872c4d8865e8f5beceb04ff Mon Sep 17 00:00:00 2001 From: Nick Sullivan Date: Tue, 24 Feb 2015 17:55:25 -0800 Subject: [PATCH] crypto/rsa: implement crypto.Decrypter Decrypter is an interface to support opaque private keys that perform decryption operations. This interface is analogous to the crypto.Signer interface. This change introduces the crypto.Decrypter interface and implements the crypto.Decrypter interface for rsa.PrivateKey with both OAEP and PKCS#1 v1.5 padding modes. Change-Id: I433f649f84ed3c2148337d735cafd75f1d94a904 Reviewed-on: https://go-review.googlesource.com/3900 Reviewed-by: Adam Langley --- src/crypto/crypto.go | 16 ++++++++++++ src/crypto/rsa/pkcs1v15.go | 10 ++++++++ src/crypto/rsa/pkcs1v15_test.go | 43 +++++++++++++++++++++++++++------ src/crypto/rsa/rsa.go | 41 +++++++++++++++++++++++++++++++ 4 files changed, 102 insertions(+), 8 deletions(-) diff --git a/src/crypto/crypto.go b/src/crypto/crypto.go index 59b23e93f5..183a4bd313 100644 --- a/src/crypto/crypto.go +++ b/src/crypto/crypto.go @@ -124,3 +124,19 @@ type SignerOpts interface { // hashing was done. HashFunc() Hash } + +// Decrypter is an interface for an opaque private key that can be used for +// asymmetric decryption operations. For example, an RSA key kept in a hardware +// module. +type Decrypter interface { + // Public returns the public key corresponding to the opaque, + // private key. + Public() PublicKey + + // Decrypt decrypts msg. The opts argument should be appropriate for + // the primitive used. See the documentation in each implementation for + // details. + Decrypt(rand io.Reader, msg []byte, opts DecrypterOpts) (plaintext []byte, err error) +} + +type DecrypterOpts interface{} diff --git a/src/crypto/rsa/pkcs1v15.go b/src/crypto/rsa/pkcs1v15.go index 59e8bb5b7b..34037b0d67 100644 --- a/src/crypto/rsa/pkcs1v15.go +++ b/src/crypto/rsa/pkcs1v15.go @@ -14,6 +14,16 @@ import ( // This file implements encryption and decryption using PKCS#1 v1.5 padding. +// PKCS1v15DecrypterOpts is for passing options to PKCS#1 v1.5 decryption using +// the crypto.Decrypter interface. +type PKCS1v15DecryptOptions struct { + // SessionKeyLen is the length of the session key that is being + // decrypted. If not zero, then a padding error during decryption will + // cause a random plaintext of this length to be returned rather than + // an error. These alternatives happen in constant time. + SessionKeyLen int +} + // EncryptPKCS1v15 encrypts the given message with RSA and the padding scheme from PKCS#1 v1.5. // The message must be no longer than the length of the public modulus minus 11 bytes. // WARNING: use of this function to encrypt plaintexts other than session keys diff --git a/src/crypto/rsa/pkcs1v15_test.go b/src/crypto/rsa/pkcs1v15_test.go index 2dc5dbc2c8..89253751ec 100644 --- a/src/crypto/rsa/pkcs1v15_test.go +++ b/src/crypto/rsa/pkcs1v15_test.go @@ -51,14 +51,25 @@ var decryptPKCS1v15Tests = []DecryptPKCS1v15Test{ } func TestDecryptPKCS1v15(t *testing.T) { - for i, test := range decryptPKCS1v15Tests { - out, err := DecryptPKCS1v15(nil, rsaPrivateKey, decodeBase64(test.in)) - if err != nil { - t.Errorf("#%d error decrypting", i) - } - want := []byte(test.out) - if !bytes.Equal(out, want) { - t.Errorf("#%d got:%#v want:%#v", i, out, want) + decryptionFuncs := []func([]byte) ([]byte, error){ + func(ciphertext []byte) (plaintext []byte, err error) { + return DecryptPKCS1v15(nil, rsaPrivateKey, ciphertext) + }, + func(ciphertext []byte) (plaintext []byte, err error) { + return rsaPrivateKey.Decrypt(nil, ciphertext, nil) + }, + } + + for _, decryptFunc := range decryptionFuncs { + for i, test := range decryptPKCS1v15Tests { + out, err := decryptFunc(decodeBase64(test.in)) + if err != nil { + t.Errorf("#%d error decrypting", i) + } + want := []byte(test.out) + if !bytes.Equal(out, want) { + t.Errorf("#%d got:%#v want:%#v", i, out, want) + } } } } @@ -138,6 +149,22 @@ func TestEncryptPKCS1v15SessionKey(t *testing.T) { } } +func TestEncryptPKCS1v15DecrypterSessionKey(t *testing.T) { + for i, test := range decryptPKCS1v15SessionKeyTests { + plaintext, err := rsaPrivateKey.Decrypt(rand.Reader, decodeBase64(test.in), &PKCS1v15DecryptOptions{SessionKeyLen: 4}) + if err != nil { + t.Fatalf("#%d: error decrypting: %s", i, err) + } + if len(plaintext) != 4 { + t.Fatalf("#%d: incorrect length plaintext: got %d, want 4", i, len(plaintext)) + } + + if test.out != "FAIL" && !bytes.Equal(plaintext, []byte(test.out)) { + t.Errorf("#%d: incorrect plaintext: got %x, want %x", plaintext, test.out) + } + } +} + func TestNonZeroRandomBytes(t *testing.T) { random := rand.Reader diff --git a/src/crypto/rsa/rsa.go b/src/crypto/rsa/rsa.go index 21704469d2..f9f6d25a89 100644 --- a/src/crypto/rsa/rsa.go +++ b/src/crypto/rsa/rsa.go @@ -24,6 +24,16 @@ type PublicKey struct { E int // public exponent } +// OAEPOptions is an interface for passing options to OAEP decryption using the +// crypto.Decrypter interface. +type OAEPOptions struct { + // Hash is the hash function that will be used when generating the mask. + Hash crypto.Hash + // Label is an arbitrary byte string that must be equal to the value + // used when encrypting. + Label []byte +} + var ( errPublicModulus = errors.New("crypto/rsa: missing public modulus") errPublicExponentSmall = errors.New("crypto/rsa: public exponent too small") @@ -77,6 +87,37 @@ func (priv *PrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) return SignPKCS1v15(rand, priv, opts.HashFunc(), msg) } +// Decrypt decrypts ciphertext with priv. If opts is nil or of type +// *PKCS1v15DecryptOptions then PKCS#1 v1.5 decryption is performed. Otherwise +// opts must have type *OAEPOptions and OAEP decryption is done. +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: + return DecryptOAEP(opts.Hash.New(), rand, priv, ciphertext, opts.Label) + + case *PKCS1v15DecryptOptions: + if l := opts.SessionKeyLen; l > 0 { + plaintext = make([]byte, l) + if _, err := rand.Read(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") + } +} + type PrecomputedValues struct { Dp, Dq *big.Int // D mod (P-1) (or mod Q-1) Qinv *big.Int // Q^-1 mod P -- 2.48.1