From 41fa304f70717563873547fc2651b487ecec6549 Mon Sep 17 00:00:00 2001 From: Sergey Cherepanov Date: Fri, 6 Jan 2023 21:39:36 +0300 Subject: [PATCH] crypro: DeriveRSA --- go.mod | 2 +- go.sum | 4 + util/keys/asymmetric/encryptionkey/rsa.go | 155 ++++++++++++++++++ .../keys/asymmetric/encryptionkey/rsa_test.go | 24 +++ 4 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 util/keys/asymmetric/encryptionkey/rsa_test.go diff --git a/go.mod b/go.mod index c0bb5cab..12b06522 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/zeebo/errs v1.3.0 go.uber.org/atomic v1.10.0 go.uber.org/zap v1.24.0 - golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b + golang.org/x/exp v0.0.0-20230105202349-8879d0199aa3 gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 gopkg.in/yaml.v3 v3.0.1 storj.io/drpc v0.0.32 diff --git a/go.sum b/go.sum index cfd087e3..0e52e5ce 100644 --- a/go.sum +++ b/go.sum @@ -505,6 +505,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b h1:SCE/18RnFsLrjydh/R/s5EVvHoZprqEQUuoxK8q2Pc4= golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/exp v0.0.0-20230105202349-8879d0199aa3 h1:fJwx88sMf5RXwDwziL0/Mn9Wqs+efMSo/RYcL+37W9c= +golang.org/x/exp v0.0.0-20230105202349-8879d0199aa3/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20200119044424-58c23975cae1 h1:5h3ngYt7+vXCDZCup/HkCQgW5XwmSvR/nA2JmJ0RErg= @@ -529,6 +531,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -697,6 +700,7 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/util/keys/asymmetric/encryptionkey/rsa.go b/util/keys/asymmetric/encryptionkey/rsa.go index 467a22e4..80fc599a 100644 --- a/util/keys/asymmetric/encryptionkey/rsa.go +++ b/util/keys/asymmetric/encryptionkey/rsa.go @@ -8,9 +8,16 @@ import ( "crypto/x509" "errors" "github.com/anytypeio/any-sync/util/keys" + "github.com/cespare/xxhash" + mrand "golang.org/x/exp/rand" "io" + "math" + "math/big" ) +var bigZero = big.NewInt(0) +var bigOne = big.NewInt(1) + var MinRsaKeyBits = 2048 var ErrKeyLengthTooSmall = errors.New("error key length too small") @@ -80,6 +87,19 @@ func GenerateRSAKeyPair(bits int, src io.Reader) (PrivKey, PubKey, error) { return &EncryptionRsaPrivKey{privKey: *priv}, &EncryptionRsaPubKey{pubKey: pk}, nil } +func DeriveRSAKePair(bits int, seed []byte) (PrivKey, PubKey, error) { + if bits < MinRsaKeyBits { + return nil, nil, ErrKeyLengthTooSmall + } + seed64 := xxhash.Sum64(seed) + priv, err := rsaGenerateMultiPrimeKey(mrand.New(mrand.NewSource(seed64)), 2, bits) + if err != nil { + return nil, nil, err + } + pk := priv.PublicKey + return &EncryptionRsaPrivKey{privKey: *priv}, &EncryptionRsaPubKey{pubKey: pk}, nil +} + func NewEncryptionRsaPrivKeyFromBytes(bytes []byte) (PrivKey, error) { sk, err := x509.ParsePKCS1PrivateKey(bytes) if err != nil { @@ -118,3 +138,138 @@ func keyEquals(k1, k2 keys.Key) bool { } return subtle.ConstantTimeCompare(a, b) == 1 } + +// generateMultiPrimeKey is a copied original rsa.GenerateMultiPrimeKey but without randutil.MaybeReadByte calls +func rsaGenerateMultiPrimeKey(random io.Reader, nprimes int, bits int) (*rsa.PrivateKey, error) { + + priv := new(rsa.PrivateKey) + priv.E = 65537 + + if nprimes < 2 { + return nil, errors.New("crypto/rsa: GenerateMultiPrimeKey: nprimes must be >= 2") + } + + if bits < 64 { + primeLimit := float64(uint64(1) << uint(bits/nprimes)) + // pi approximates the number of primes less than primeLimit + pi := primeLimit / (math.Log(primeLimit) - 1) + // Generated primes start with 11 (in binary) so we can only + // use a quarter of them. + pi /= 4 + // Use a factor of two to ensure that key generation terminates + // in a reasonable amount of time. + pi /= 2 + if pi <= float64(nprimes) { + return nil, errors.New("crypto/rsa: too few primes of given length to generate an RSA key") + } + } + + primes := make([]*big.Int, nprimes) + +NextSetOfPrimes: + for { + todo := bits + // crypto/rand should set the top two bits in each prime. + // Thus each prime has the form + // p_i = 2^bitlen(p_i) × 0.11... (in base 2). + // And the product is: + // P = 2^todo × α + // where α is the product of nprimes numbers of the form 0.11... + // + // If α < 1/2 (which can happen for nprimes > 2), we need to + // shift todo to compensate for lost bits: the mean value of 0.11... + // is 7/8, so todo + shift - nprimes * log2(7/8) ~= bits - 1/2 + // will give good results. + if nprimes >= 7 { + todo += (nprimes - 2) / 5 + } + for i := 0; i < nprimes; i++ { + var err error + primes[i], err = randPrime(random, todo/(nprimes-i)) + if err != nil { + return nil, err + } + todo -= primes[i].BitLen() + } + + // Make sure that primes is pairwise unequal. + for i, prime := range primes { + for j := 0; j < i; j++ { + if prime.Cmp(primes[j]) == 0 { + continue NextSetOfPrimes + } + } + } + + n := new(big.Int).Set(bigOne) + totient := new(big.Int).Set(bigOne) + pminus1 := new(big.Int) + for _, prime := range primes { + n.Mul(n, prime) + pminus1.Sub(prime, bigOne) + totient.Mul(totient, pminus1) + } + if n.BitLen() != bits { + // This should never happen for nprimes == 2 because + // crypto/rand should set the top two bits in each prime. + // For nprimes > 2 we hope it does not happen often. + continue NextSetOfPrimes + } + + priv.D = new(big.Int) + e := big.NewInt(int64(priv.E)) + ok := priv.D.ModInverse(e, totient) + + if ok != nil { + priv.Primes = primes + priv.N = n + break + } + } + + priv.Precompute() + return priv, nil +} + +func randPrime(rand io.Reader, bits int) (*big.Int, error) { + if bits < 2 { + return nil, errors.New("crypto/rand: prime size must be at least 2-bit") + } + + b := uint(bits % 8) + if b == 0 { + b = 8 + } + + bytes := make([]byte, (bits+7)/8) + p := new(big.Int) + + for { + if _, err := io.ReadFull(rand, bytes); err != nil { + return nil, err + } + + // Clear bits in the first byte to make sure the candidate has a size <= bits. + bytes[0] &= uint8(int(1<= 2 { + bytes[0] |= 3 << (b - 2) + } else { + // Here b==1, because b cannot be zero. + bytes[0] |= 1 + if len(bytes) > 1 { + bytes[1] |= 0x80 + } + } + // Make the value odd since an even number this large certainly isn't prime. + bytes[len(bytes)-1] |= 1 + + p.SetBytes(bytes) + if p.ProbablyPrime(20) { + return p, nil + } + } +} diff --git a/util/keys/asymmetric/encryptionkey/rsa_test.go b/util/keys/asymmetric/encryptionkey/rsa_test.go new file mode 100644 index 00000000..55b2bfd8 --- /dev/null +++ b/util/keys/asymmetric/encryptionkey/rsa_test.go @@ -0,0 +1,24 @@ +package encryptionkey + +import ( + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "testing" +) + +func TestDeriveRSAKePair(t *testing.T) { + privKey1, _, err := DeriveRSAKePair(4096, []byte("test seed")) + require.NoError(t, err) + + privKey2, _, err := DeriveRSAKePair(4096, []byte("test seed")) + require.NoError(t, err) + data := []byte("test data") + + encryped, err := privKey1.GetPublic().Encrypt(data) + require.NoError(t, err) + + decrypted, err := privKey2.Decrypt(encryped) + require.NoError(t, err) + + assert.Equal(t, data, decrypted) +}