This repository has been archived on 2024-02-27. You can view files and clone it, but cannot push or open issues or pull requests.
gootp/internal/andotp.go

128 lines
3.1 KiB
Go

package internal
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha1"
"encoding/binary"
"encoding/json"
"fmt"
"math/big"
"golang.org/x/crypto/pbkdf2"
)
const (
ivLen = 12
keyLen = 32
iterationLen = 4
saltLen = 12
maxIterations = 160000
minIterations = 140000
)
func Encrypt(plaintext, password []byte) ([]byte, error) {
iter := make([]byte, iterationLen)
iv := make([]byte, ivLen)
salt := make([]byte, saltLen)
iterations, err := randIterations()
if err != nil {
return nil, fmt.Errorf("randIterations: %w", err)
}
binary.BigEndian.PutUint32(iter, uint32(iterations))
if _, err = rand.Read(iv); err != nil {
return nil, fmt.Errorf("rand.Read(iv): %w", err)
}
if _, err = rand.Read(salt); err != nil {
return nil, fmt.Errorf("rand.Read(salt): %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 randIterations() (int, error) {
iterations, err := rand.Int(rand.Reader, big.NewInt(int64(maxIterations-minIterations)))
if err != nil {
return 0, fmt.Errorf("rand.Int: %w", err)
}
return int(iterations.Int64() + minIterations), nil
}
func Decrypt(text, password []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
}
type Entry struct {
Algorithm string `json:"algorithm"`
Digits uint8 `json:"digits"`
Issuer string `json:"issuer"`
Label string `json:"label"`
Period uint32 `json:"period"`
Secret string `json:"secret"`
Tags []string `json:"tags"`
Thumbnail string `json:"thumbnail"`
Type string `json:"type"`
}
func DecryptAsEntries(text, password []byte) ([]Entry, error) {
result, err := Decrypt(text, password)
if err != nil {
return nil, fmt.Errorf("decrypt: %w", err)
}
var entries []Entry
if err = json.Unmarshal(result, &entries); err != nil {
return nil, fmt.Errorf("json.Unmarshal: %w", err)
}
return entries, nil
}