-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhybrid.go
121 lines (100 loc) · 3.2 KB
/
hybrid.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package crypt
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"github.com/pkg/errors"
)
type HybridDecrypter struct {
privateKey *rsa.PrivateKey
}
// Decrypt decrypts given encrypted data
func (d *HybridDecrypter) Decrypt(encrypted []byte) ([]byte, error) {
cipherText, err := base64Decode(encrypted)
if err != nil {
return nil, errors.Wrap(err, "failed to decode base64")
}
aesKeyLength := d.privateKey.PublicKey.Size()
if len(cipherText) <= aesKeyLength {
return nil, errors.Errorf("encrypted data length too low")
}
hash := sha256.New()
aesKey, err := rsa.DecryptOAEP(hash, rand.Reader, d.privateKey, cipherText[:aesKeyLength], nil)
if err != nil {
return nil, errors.Wrap(err, "failed to decrypt aes key with private Key")
}
gcm, err := aesGCM(aesKey)
if err != nil {
return nil, err
}
textStart := aesKeyLength + gcm.NonceSize()
// cipherText[:0] reuses allocated slice
plainText, err := gcm.Open([]byte{}, cipherText[aesKeyLength:textStart], cipherText[textStart:], nil)
if err != nil {
return nil, errors.Wrapf(err, "failed to decrypt aes data")
}
return plainText, nil
}
var _ Encrypter = &HybridEncrypter{}
// HybridEncrypter implements Encrypter with RSA for encrypting AES key and AES-256 for encrypting raw data.
// SHA256 used as hash function for RSA
type HybridEncrypter struct {
HybridDecrypter
publicKey *rsa.PublicKey
}
// Encrypts encrypts given plain data
func (e *HybridEncrypter) Encrypt(plain []byte) ([]byte, error) {
hash := sha256.New()
// 32 bytes for AES-256 encryption
aesKey := make([]byte, 32)
if err := fillRand(aesKey); err != nil {
return nil, errors.Wrapf(err, "failed to generate aes key")
}
gcm, err := aesGCM(aesKey)
if err != nil {
return nil, err
}
nonce := make([]byte, gcm.NonceSize())
if err := fillRand(nonce); err != nil {
return nil, errors.Wrapf(err, "failed to generate nonce for aes gcm cipher")
}
cipherText := gcm.Seal([]byte{}, nonce, plain, nil)
encryptedAes, err := rsa.EncryptOAEP(hash, rand.Reader, e.publicKey, aesKey, nil)
if err != nil {
return nil, errors.Wrap(err, "failed to encrypt aes key with public key")
}
return base64Encode(append(encryptedAes, append(nonce, cipherText...)...)), nil
}
func NewHybridDecrypter(privateKey *rsa.PrivateKey) *HybridDecrypter {
return &HybridDecrypter{privateKey: privateKey}
}
func NewHybridEncrypter(privateKey *rsa.PrivateKey, publicKey *rsa.PublicKey) *HybridEncrypter {
return &HybridEncrypter{
HybridDecrypter: HybridDecrypter{privateKey: privateKey},
publicKey: publicKey,
}
}
func aesGCM(aesKey []byte) (cipher.AEAD, error) {
block, err := aes.NewCipher(aesKey)
if err != nil {
return nil, errors.Wrapf(err, "failed to build new aes cipher from key %s", string(aesKey))
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, errors.Wrapf(err, "failed to build new gcm from aes cipher %+v", block)
}
return gcm, nil
}
func fillRand(buffer []byte) error {
n, err := rand.Read(buffer)
bufLen := len(buffer)
if err != nil {
return errors.Wrapf(err, "failed to read %d bytes from crypto/Rand", bufLen)
}
if n != bufLen {
return errors.Errorf("read only %d of %d bytes from crypto/Rand", n, bufLen)
}
return nil
}