96 lines
2.3 KiB
Go
96 lines
2.3 KiB
Go
|
package andotp
|
||
|
|
||
|
import (
|
||
|
"crypto/aes"
|
||
|
"crypto/cipher"
|
||
|
"crypto/rand"
|
||
|
"crypto/sha1"
|
||
|
"encoding/binary"
|
||
|
"fmt"
|
||
|
"math/big"
|
||
|
|
||
|
"golang.org/x/crypto/pbkdf2"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
ivLen = 12
|
||
|
keyLen = 32
|
||
|
iterationLen = 4
|
||
|
saltLen = 12
|
||
|
maxIterations = 160000
|
||
|
minIterations = 140000
|
||
|
)
|
||
|
|
||
|
func Encrypt(password, plaintext []byte) ([]byte, error) {
|
||
|
iter := make([]byte, iterationLen)
|
||
|
iv := make([]byte, ivLen)
|
||
|
salt := make([]byte, saltLen)
|
||
|
|
||
|
maxMinIterationsSubtracted, err := rand.Int(rand.Reader, big.NewInt(int64(maxIterations-minIterations)))
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("rand.Int: %w", err)
|
||
|
}
|
||
|
|
||
|
iterations := int(maxMinIterationsSubtracted.Int64() + minIterations)
|
||
|
binary.BigEndian.PutUint32(iter, uint32(iterations))
|
||
|
|
||
|
_, err = rand.Read(iv)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("rand.Read: %w", err)
|
||
|
}
|
||
|
|
||
|
_, err = rand.Read(salt)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("rand.Read: %w", err)
|
||
|
}
|
||
|
|
||
|
secretKey := pbkdf2.Key(password, salt, iterations, keyLen, sha1.New)
|
||
|
|
||
|
block, err := aes.NewCipher(secretKey)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("aes.NewCipher: %w", err)
|
||
|
}
|
||
|
|
||
|
aesGCM, err := cipher.NewGCM(block)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("cipher.NewGCM: %w", err)
|
||
|
}
|
||
|
|
||
|
cipherText := aesGCM.Seal(nil, iv, plaintext, nil)
|
||
|
|
||
|
finalCipher := make([]byte, 0, len(iter)+len(salt)+len(iv)+len(cipherText))
|
||
|
finalCipher = append(finalCipher, iter...)
|
||
|
finalCipher = append(finalCipher, salt...)
|
||
|
finalCipher = append(finalCipher, iv...)
|
||
|
finalCipher = append(finalCipher, cipherText...)
|
||
|
|
||
|
return finalCipher, nil
|
||
|
|
||
|
}
|
||
|
|
||
|
func Decrypt(password, text []byte) ([]byte, error) {
|
||
|
iterations := text[:iterationLen]
|
||
|
salt := text[iterationLen : iterationLen+saltLen]
|
||
|
iv := text[iterationLen+saltLen : iterationLen+saltLen+ivLen]
|
||
|
cipherText := text[iterationLen+saltLen+ivLen:]
|
||
|
iter := int(binary.BigEndian.Uint32(iterations))
|
||
|
secretKey := pbkdf2.Key(password, salt, iter, keyLen, sha1.New)
|
||
|
|
||
|
block, err := aes.NewCipher(secretKey)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("aes.NewCipher: %w", err)
|
||
|
}
|
||
|
|
||
|
aesGCM, err := cipher.NewGCM(block)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("cipher.NewGCM: %w", err)
|
||
|
}
|
||
|
|
||
|
plaintext, err := aesGCM.Open(nil, iv, cipherText, nil)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("aesGCM.Open: %w", err)
|
||
|
}
|
||
|
|
||
|
return plaintext, nil
|
||
|
}
|