-
Notifications
You must be signed in to change notification settings - Fork 684
/
crypto.go
123 lines (101 loc) · 3.18 KB
/
crypto.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
122
123
package crypto
import (
"crypto/aes"
"crypto/cipher"
"crypto/rsa"
"crypto/sha256"
"encoding/binary"
"errors"
"fmt"
"io"
"golang.org/x/crypto/ssh"
)
const (
sessionKeyBytes = 32
)
// ErrTooShort indicates the provided data is too short to be valid.
var ErrTooShort = errors.New("SealedSecret data is too short")
// PublicKeyFingerprint returns a fingerprint for a public key.
func PublicKeyFingerprint(rp *rsa.PublicKey) (string, error) {
sp, err := ssh.NewPublicKey(rp)
if err != nil {
return "", err
}
return ssh.FingerprintSHA256(sp), nil
}
// HybridEncrypt performs a regular AES-GCM + RSA-OAEP encryption.
// The output byte string is:
//
// RSA ciphertext length || RSA ciphertext || AES ciphertext
func HybridEncrypt(rnd io.Reader, pubKey *rsa.PublicKey, plaintext, label []byte) ([]byte, error) {
// Generate a random symmetric key
sessionKey := make([]byte, sessionKeyBytes)
if _, err := io.ReadFull(rnd, sessionKey); err != nil {
return nil, err
}
block, err := aes.NewCipher(sessionKey)
if err != nil {
return nil, err
}
aed, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
// Encrypt symmetric key
rsaCiphertext, err := rsa.EncryptOAEP(sha256.New(), rnd, pubKey, sessionKey, label)
if err != nil {
return nil, err
}
// First 2 bytes are RSA ciphertext length, so we can separate
// all the pieces later.
ciphertext := make([]byte, 2)
binary.BigEndian.PutUint16(ciphertext, uint16(len(rsaCiphertext)))
ciphertext = append(ciphertext, rsaCiphertext...)
// SessionKey is only used once, so zero nonce is ok
zeroNonce := make([]byte, aed.NonceSize())
// Append symmetrically encrypted Secret
ciphertext = aed.Seal(ciphertext, zeroNonce, plaintext, nil)
return ciphertext, nil
}
// HybridDecrypt performs a regular AES-GCM + RSA-OAEP decryption.
// The private keys map has a fingerprint of each public key as the map key.
func HybridDecrypt(rnd io.Reader, privKeys map[string]*rsa.PrivateKey, ciphertext, label []byte) ([]byte, error) {
// TODO(mkm): use the key fingerprint encoded in ciphertext (if present) instead of trying all the possible keys
for _, privKey := range privKeys {
if secret, err := singleDecrypt(rnd, privKey, ciphertext, label); err == nil {
return secret, nil
}
}
return nil, fmt.Errorf("no key could decrypt secret")
}
// singleDecrypt performs a regular AES-GCM + RSA-OAEP decryption.
func singleDecrypt(rnd io.Reader, privKey *rsa.PrivateKey, ciphertext, label []byte) ([]byte, error) {
if len(ciphertext) < 2 {
return nil, ErrTooShort
}
rsaLen := int(binary.BigEndian.Uint16(ciphertext))
if len(ciphertext) < rsaLen+2 {
return nil, ErrTooShort
}
rsaCiphertext := ciphertext[2 : rsaLen+2]
aesCiphertext := ciphertext[rsaLen+2:]
sessionKey, err := rsa.DecryptOAEP(sha256.New(), rnd, privKey, rsaCiphertext, label)
if err != nil {
return nil, err
}
block, err := aes.NewCipher(sessionKey)
if err != nil {
return nil, err
}
aed, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
// Key is only used once, so zero nonce is ok
zeroNonce := make([]byte, aed.NonceSize())
plaintext, err := aed.Open(nil, zeroNonce, aesCiphertext, nil)
if err != nil {
return nil, err
}
return plaintext, nil
}