diff --git a/README.md b/README.md index a2731da..e266edb 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ A modern, comprehensive and efficient utility library of Go. - Comprehensive, efficient and reusable. - Numerous util functions, support string, slice, map, datetime, crypto... -- Only depend on the go standard library. +- Only depend on the go standard and golang.org/x library. - High unit test coverage for exported functions. # Encoding @@ -295,17 +295,24 @@ HMACSHA512("", "") // B936CEE86C9F87AA5D3C6F2E84CB5A4239A5FE50480A6EC66B70AB5B1F // Encryption functions. p := []byte("plaintext") -key16 := []byte("12345678abcdefgh") -c, _ := Base64AESCBCEncrypt(p, key16) // A67NhD3RBiNaMgG6HTm8LQ== -p, _ = Base64AESCBCDecrypt(c, key16) // plaintext - key8 := []byte("12345678") -c, _ := Base64DESCBCEncrypt(p, key8) // UZS/y4By6ksePYMBbvZdig== -p, _ := Base64DESCBCDecrypt(c, key8) // plaintext +Base64DESCBCEncrypt(p, key8) // UZS/y4By6ksePYMBbvZdig== +Base64DESCBCDecrypt(c, key8) // plaintext key24 := []byte("12345678abcdefgh12345678") -c, _ := Base64TriDESCBCEncrypt(p, key24) // dau0DzmDGQbHasZaOvxxwg== -p, _ := Base64TriDESCBCDecrypt(c, key24) // plaintext +Base64TriDESCBCEncrypt(p, key24) // dau0DzmDGQbHasZaOvxxwg== +Base64TriDESCBCDecrypt(c, key24) // plaintext + +key16 := []byte("12345678abcdefgh") +Base64AESCBCEncrypt(p, key16) // A67NhD3RBiNaMgG6HTm8LQ== +Base64AESCBCDecrypt(c, key16) // plaintext + +// RSA encryption, decryption, sign and verify signature. +GenRsaKey(bits int) (prvkey, pubkey []byte, err error) +RsaEncrypt(pubkey, data []byte) ([]byte, error) +RsaDecrypt(prvkey, cipher []byte) ([]byte, error) +RsaSign(prvkey []byte, hash crypto.Hash, data []byte) ([]byte, error) +RsaVerifySign(pubkey []byte, hash crypto.Hash, data, sig []byte) error ``` # Rand diff --git a/crypto/rsa.go b/crypto/rsa.go new file mode 100644 index 0000000..2eece65 --- /dev/null +++ b/crypto/rsa.go @@ -0,0 +1,126 @@ +package crypto + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/sha512" + "crypto/x509" + "encoding/pem" + "errors" +) + +// GenRsaKey generates an PKCS#1 RSA keypair of the given bit size in PEM format. +func GenRsaKey(bits int) (prvkey, pubkey []byte, err error) { + // Generates private key. + privateKey, err := rsa.GenerateKey(rand.Reader, bits) + if err != nil { + return + } + derStream := x509.MarshalPKCS1PrivateKey(privateKey) + block := &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: derStream, + } + prvkey = pem.EncodeToMemory(block) + + // Generates public key from private key. + publicKey := &privateKey.PublicKey + derPkix, err := x509.MarshalPKIXPublicKey(publicKey) + if err != nil { + return + } + block = &pem.Block{ + Type: "RSA PUBLIC KEY", + Bytes: derPkix, + } + pubkey = pem.EncodeToMemory(block) + return +} + +// RsaEncrypt encrypts data using rsa public key. +func RsaEncrypt(pubkey, data []byte) ([]byte, error) { + block, _ := pem.Decode(pubkey) + if block == nil { + return nil, errors.New("decode public key error") + } + pub, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return nil, err + } + return rsa.EncryptPKCS1v15(rand.Reader, pub.(*rsa.PublicKey), data) +} + +// RsaDecrypt decrypts data using rsa private key. +func RsaDecrypt(prvkey, cipher []byte) ([]byte, error) { + block, _ := pem.Decode(prvkey) + if block == nil { + return nil, errors.New("decode private key error") + } + prv, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + return rsa.DecryptPKCS1v15(rand.Reader, prv, cipher) +} + +// RsaSign signs using private key in PEM format. +func RsaSign(prvkey []byte, hash crypto.Hash, data []byte) ([]byte, error) { + block, _ := pem.Decode(prvkey) + if block == nil { + return nil, errors.New("decode private key error") + } + privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + + // MD5 and SHA1 are not supported as they are not secure. + var hashed []byte + switch hash { + case crypto.SHA224: + h := sha256.Sum224(data) + hashed = h[:] + case crypto.SHA256: + h := sha256.Sum256(data) + hashed = h[:] + case crypto.SHA384: + h := sha512.Sum384(data) + hashed = h[:] + case crypto.SHA512: + h := sha512.Sum512(data) + hashed = h[:] + } + return rsa.SignPKCS1v15(rand.Reader, privateKey, hash, hashed) +} + +// RsaVerifySign verifies signature using public key in PEM format. +func RsaVerifySign(pubkey []byte, hash crypto.Hash, data, sig []byte) error { + block, _ := pem.Decode(pubkey) + if block == nil { + return errors.New("decode public key error") + } + pub, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return err + } + + // SHA1 and MD5 are not supported as they are not secure. + var hashed []byte + switch hash { + case crypto.SHA224: + h := sha256.Sum224(data) + hashed = h[:] + case crypto.SHA256: + h := sha256.Sum256(data) + hashed = h[:] + case crypto.SHA384: + h := sha512.Sum384(data) + hashed = h[:] + case crypto.SHA512: + h := sha512.Sum512(data) + hashed = h[:] + } + return rsa.VerifyPKCS1v15(pub.(*rsa.PublicKey), hash, hashed, sig) +} diff --git a/crypto/rsa_test.go b/crypto/rsa_test.go new file mode 100644 index 0000000..a57d591 --- /dev/null +++ b/crypto/rsa_test.go @@ -0,0 +1,73 @@ +package crypto + +import ( + "crypto" + "testing" + + "github.com/dablelv/go-huge-util/internal" +) + +func TestRsaEncryptDecrypt(t *testing.T) { + assert := internal.NewAssert(t, "RsaEncryptDecrypt") + + data := []byte("foo") + + // Encrypt and decrypt data using 2048 bits length key. + prvkey, pubkey, err := GenRsaKey(2048) + assert.IsNil(err) + + // Encrypt data. + cipher, err := RsaEncrypt(pubkey, data) + assert.IsNil(err) + assert.Equal(2048/8, len(cipher)) + + // Decrypt data. + plain, err := RsaDecrypt(prvkey, cipher) + assert.IsNil(err) + assert.Equal(data, plain) + + // Encrypt and decrypt data using 4096 bits length key. + prvkey, pubkey, err = GenRsaKey(4096) + assert.IsNil(err) + + // Encrypt data. + cipher, err = RsaEncrypt(pubkey, data) + assert.IsNil(err) + assert.Equal(4096/8, len(cipher)) + + // Decrypt data. + plain, err = RsaDecrypt(prvkey, cipher) + assert.IsNil(err) + assert.Equal(data, plain) + + // Encrypt and decrypt data using 8192 bits length key. + prvkey, pubkey, err = GenRsaKey(8192) + assert.IsNil(err) + + // Encrypt data. + cipher, err = RsaEncrypt(pubkey, data) + assert.IsNil(err) + assert.Equal(8192/8, len(cipher)) + + // Decrypt data. + plain, err = RsaDecrypt(prvkey, cipher) + assert.IsNil(err) + assert.Equal(data, plain) +} + +func TestRsaSignAndVerify(t *testing.T) { + assert := internal.NewAssert(t, "RsaSignAndVerify") + + msg := []byte("foo") + prvkey, pubkey, err := GenRsaKey(2048) + assert.IsNil(err) + + // Using SHA256 to hash msg and then use rsa private key to sign. + sig, err := RsaSign(prvkey, crypto.SHA256, msg) + assert.IsNil(err) + assert.Equal(2048/8, len(sig)) + + // Using public key to verify signature. + err = RsaVerifySign(pubkey, crypto.SHA256, msg, sig) + assert.IsNil(err) +} diff --git a/go.mod b/go.mod index 7ea9f78..2456bc1 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/dablelv/go-huge-util +module github.com/dablelv/cyan go 1.19