Skip to content

Commit

Permalink
Add cryptographic functions (#176)
Browse files Browse the repository at this point in the history
  • Loading branch information
vminkobin authored Oct 17, 2022
1 parent a4530cb commit 695ff13
Show file tree
Hide file tree
Showing 2 changed files with 202 additions and 0 deletions.
117 changes: 117 additions & 0 deletions crypto/sign.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package crypto

import (
"crypto"
"crypto/hmac"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io"
"os"
"strings"
)

// Signer is an interface that can be used to sign messages
type Signer interface {
Sign(msg []byte) ([]byte, error)
}

// SignFunc is a wrapper of sign functions to implement Signer interface
type SignFunc func(msg []byte) ([]byte, error)

func (sf SignFunc) Sign(msg []byte) ([]byte, error) {
return sf(msg)
}

// NewSHA256WithRSASigner returns Signer instance which signs msg using SHA256WithRSA and private key
func NewSHA256WithRSASigner(privateKey *rsa.PrivateKey) SignFunc {
return func(msg []byte) ([]byte, error) {
return SHA256WithRSA(msg, privateKey)
}
}

// NewHMACSHA256Signer returns Signer instance which signs msg using HMACSHA256S and key
func NewHMACSHA256Signer(key string) SignFunc {
return func(msg []byte) ([]byte, error) {
return HMACSHA256(msg, key)
}
}

// SHA256WithRSA signs SHA256 hash of the message with RSA privateKey
func SHA256WithRSA(msg []byte, privateKey *rsa.PrivateKey) ([]byte, error) {
if privateKey == nil {
return nil, errors.New("private key is empty")
}

h := sha256.New()
_, err := h.Write(msg)
if err != nil {
return nil, fmt.Errorf("write bytes: %v", err)
}

res, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, h.Sum(nil))
if err != nil {
return nil, fmt.Errorf("SignPKCS1v15: %v", err)
}

return res, nil
}

// HMACSHA256 signs message with HMAC SHA256 using key
func HMACSHA256(msg []byte, key string) ([]byte, error) {
h := hmac.New(sha256.New, []byte(key))
_, err := h.Write(msg)
if err != nil {
return nil, fmt.Errorf("hmac write: %v", err)
}
return h.Sum(nil), nil
}

// GetRSAPrivateKey reads RSA private key from the reader
func GetRSAPrivateKey(reader io.Reader) (*rsa.PrivateKey, error) {
bs, err := io.ReadAll(reader)
if err != nil {
return nil, fmt.Errorf("read bytes: %v", err)
}

privPem, _ := pem.Decode(bs)
if privPem == nil {
return nil, errors.New("decoded key is empty")
}

if privPem.Type != "RSA PRIVATE KEY" {
return nil, errors.New("key type is not RSA private key")
}

var parsedKey interface{}
parsedKey, err = x509.ParsePKCS1PrivateKey(privPem.Bytes)
if err != nil {
return nil, fmt.Errorf("parse PKCS1 private key: %v", err)
}

privateKey, ok := parsedKey.(*rsa.PrivateKey)
if !ok {
return nil, errors.New("parsed key is not RSA private key")
}

return privateKey, nil
}

// GetRSAPrivateKeyFromFile reads RSA private key from file
func GetRSAPrivateKeyFromFile(fileName string) (*rsa.PrivateKey, error) {
file, err := os.Open(fileName)
if err != nil {
return nil, fmt.Errorf("open file: %v", err)
}

return GetRSAPrivateKey(file)
}

// GetRSAPrivateKeyFromString reads RSA private key from string
func GetRSAPrivateKeyFromString(s string) (*rsa.PrivateKey, error) {
return GetRSAPrivateKey(strings.NewReader(s))
}
85 changes: 85 additions & 0 deletions crypto/sign_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package crypto

import (
"bytes"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"os"
"strings"
"testing"

"github.com/stretchr/testify/assert"
)

var privateKey = `-----BEGIN RSA PRIVATE KEY-----
MIIBOgIBAAJBAK1ASa283Iotdl+Sbp5IRNjumvuTs/r0ZSt1S/8dqe08WN2GiDXn
f+U1UOJPDp5qN7d+AoQSMUg2bHXeLjrxxCUCAwEAAQJAcYfJQGKcmqfEBEju2CY/
h3CEewuFS5RPn7TTwi/sJJrtEkeha4CYgGJJusAr8K3J0O8EBnMtEz+KltYDWd6i
AQIhANSWLwXtb0lUqemqoslj3RKirsHac30IyyiJ45NQWp5BAiEA0KGuouUQdNbL
vso31iilbUnJJ54k1C8hREoEAqx9NOUCIQC5INByaQKw6XnOczqwBrdOsz1cs9A+
4pmJBAubDi7cAQIgOIFx4SCVQm/iovv1/4TmuSDg4GAOrYFOS0aYq3i4OJkCIAQw
PklhQYvKRwjm1jiktUyTyRHIDSVSmveZ/8N6zJSW
-----END RSA PRIVATE KEY-----
`

func testCompareKeys(t *testing.T, exp string, act *rsa.PrivateKey) {
var buf bytes.Buffer
assert.NoError(t, pem.Encode(&buf, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(act)}))

assert.Equal(t, exp, string(buf.Bytes()))
}

func TestGetRSAPrivateKey(t *testing.T) {
key, err := GetRSAPrivateKey(strings.NewReader(privateKey))
assert.NoError(t, err)

testCompareKeys(t, privateKey, key)
}

func TestHMACSHA256(t *testing.T) {
res, err := HMACSHA256([]byte("test"), "e9a9b09e-6dfb-455e-8c27-7b206bec08a1")
assert.NoError(t, err)
assert.Equal(
t,
"9e99537c0a09c501bb348bc12743707beee35eba0b1bd885de15f91bc9311047",
fmt.Sprintf("%x", string(res)),
)
}

func TestSHA256WithRSA(t *testing.T) {
key, err := GetRSAPrivateKey(strings.NewReader(privateKey))
assert.NoError(t, err)

res, err := SHA256WithRSA([]byte("test"), key)
assert.NoError(t, err)
assert.Equal(
t,
"dUiTbTPRbMhL0GyTuAE+BAbSxfEwdbWdzQuF2r3esVKg0CMtEa2btCN7O0eQezQFDRIQVXmhKRccqWPQw/Zjbw==",
base64.StdEncoding.EncodeToString(res),
)
}

func TestGetRSAPrivateKeyFromFile(t *testing.T) {
f, err := os.CreateTemp("", "test")
assert.NoError(t, err)
defer os.Remove(f.Name())

_, err = f.Write([]byte(privateKey))
assert.NoError(t, err)
assert.NoError(t, f.Sync())

key, err := GetRSAPrivateKeyFromFile(f.Name())
assert.NoError(t, err)

testCompareKeys(t, privateKey, key)
}

func TestGetRSAPrivateKeyFromString(t *testing.T) {
key, err := GetRSAPrivateKeyFromString(privateKey)
assert.NoError(t, err)

testCompareKeys(t, privateKey, key)
}

0 comments on commit 695ff13

Please sign in to comment.