1package crypt
2
3import (
4	"crypto/aes"
5	"crypto/cipher"
6	"crypto/rand"
7	"crypto/sha256"
8	"fmt"
9	"log"
10
11	"golang.org/x/crypto/argon2"
12	"golang.org/x/crypto/chacha20poly1305"
13	"golang.org/x/crypto/pbkdf2"
14)
15
16// New generates a new key based on a passphrase and salt
17func New(passphrase []byte, usersalt []byte) (key []byte, salt []byte, err error) {
18	if len(passphrase) < 1 {
19		err = fmt.Errorf("need more than that for passphrase")
20		return
21	}
22	if usersalt == nil {
23		salt = make([]byte, 8)
24		// http://www.ietf.org/rfc/rfc2898.txt
25		// Salt.
26		if _, err := rand.Read(salt); err != nil {
27			log.Fatalf("can't get random salt: %v", err)
28		}
29	} else {
30		salt = usersalt
31	}
32	key = pbkdf2.Key(passphrase, salt, 100, 32, sha256.New)
33	return
34}
35
36// Encrypt will encrypt using the pre-generated key
37func Encrypt(plaintext []byte, key []byte) (encrypted []byte, err error) {
38	// generate a random iv each time
39	// http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
40	// Section 8.2
41	ivBytes := make([]byte, 12)
42	if _, err := rand.Read(ivBytes); err != nil {
43		log.Fatalf("can't initialize crypto: %v", err)
44	}
45	b, err := aes.NewCipher(key)
46	if err != nil {
47		return
48	}
49	aesgcm, err := cipher.NewGCM(b)
50	if err != nil {
51		return
52	}
53	encrypted = aesgcm.Seal(nil, ivBytes, plaintext, nil)
54	encrypted = append(ivBytes, encrypted...)
55	return
56}
57
58// Decrypt using the pre-generated key
59func Decrypt(encrypted []byte, key []byte) (plaintext []byte, err error) {
60	if len(encrypted) < 13 {
61		err = fmt.Errorf("incorrect passphrase")
62		return
63	}
64	b, err := aes.NewCipher(key)
65	if err != nil {
66		return
67	}
68	aesgcm, err := cipher.NewGCM(b)
69	if err != nil {
70		return
71	}
72	plaintext, err = aesgcm.Open(nil, encrypted[:12], encrypted[12:], nil)
73	return
74}
75
76// NewArgon2 generates a new key based on a passphrase and salt
77// using argon2
78// https://pkg.go.dev/golang.org/x/crypto/argon2
79func NewArgon2(passphrase []byte, usersalt []byte) (aead cipher.AEAD, salt []byte, err error) {
80	if len(passphrase) < 1 {
81		err = fmt.Errorf("need more than that for passphrase")
82		return
83	}
84	if usersalt == nil {
85		salt = make([]byte, 8)
86		// http://www.ietf.org/rfc/rfc2898.txt
87		// Salt.
88		if _, err := rand.Read(salt); err != nil {
89			log.Fatalf("can't get random salt: %v", err)
90		}
91	} else {
92		salt = usersalt
93	}
94	aead, err = chacha20poly1305.NewX(argon2.IDKey(passphrase, salt, 1, 64*1024, 4, 32))
95	return
96}
97
98// EncryptChaCha will encrypt ChaCha20-Poly1305 using the pre-generated key
99// https://pkg.go.dev/golang.org/x/crypto/chacha20poly1305
100func EncryptChaCha(plaintext []byte, aead cipher.AEAD) (encrypted []byte, err error) {
101	nonce := make([]byte, aead.NonceSize(), aead.NonceSize()+len(plaintext)+aead.Overhead())
102	if _, err := rand.Read(nonce); err != nil {
103		panic(err)
104	}
105
106	// Encrypt the message and append the ciphertext to the nonce.
107	encrypted = aead.Seal(nonce, nonce, plaintext, nil)
108	return
109}
110
111// DecryptChaCha will encrypt ChaCha20-Poly1305 using the pre-generated key
112// https://pkg.go.dev/golang.org/x/crypto/chacha20poly1305
113func DecryptChaCha(encryptedMsg []byte, aead cipher.AEAD) (encrypted []byte, err error) {
114	if len(encryptedMsg) < aead.NonceSize() {
115		err = fmt.Errorf("ciphertext too short")
116		return
117	}
118
119	// Split nonce and ciphertext.
120	nonce, ciphertext := encryptedMsg[:aead.NonceSize()], encryptedMsg[aead.NonceSize():]
121
122	// Decrypt the message and check it wasn't tampered with.
123	encrypted, err = aead.Open(nil, nonce, ciphertext, nil)
124	return
125}
126