safe.go (view raw)
1package safe
2
3import (
4 "bytes"
5 "crypto/aes"
6 "crypto/cipher"
7 "crypto/rand"
8 "encoding/hex"
9 "errors"
10)
11
12const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
13
14var (
15 charsetLength = len(charset)
16)
17
18type Safe struct {
19 c cipher.Block
20}
21
22func get32ByteString() string {
23 randomBytes := make([]byte, 32)
24 _, err := rand.Read(randomBytes)
25 if err != nil {
26 panic(err)
27 }
28
29 passphrase := make([]byte, 32)
30 for i := 0; i < 32; i++ {
31 passphrase[i] = charset[int(randomBytes[i])%charsetLength]
32 }
33 return string(passphrase)
34
35}
36
37func NewSafe(password string) *Safe {
38 if len(password) != 32 {
39 println("WARNING: Using a random passphrase. Please use a fixed passphrase for production use.")
40 password = get32ByteString()
41 }
42
43 key := []byte(password)
44 c, err := aes.NewCipher(key)
45 if err != nil {
46 panic(err)
47 }
48 return &Safe{c}
49}
50
51func (s *Safe) Encrypt(plaintext string) string {
52 blockSize := s.c.BlockSize()
53 plaintextBytes := []byte(plaintext)
54
55 // PKCS#7 padding
56 padding := blockSize - len(plaintextBytes)%blockSize
57 padtext := append(plaintextBytes, bytes.Repeat([]byte{byte(padding)}, padding)...)
58
59 ciphertext := make([]byte, blockSize+len(padtext))
60 iv := ciphertext[:blockSize]
61 if _, err := rand.Read(iv); err != nil {
62 panic(err)
63 }
64
65 mode := cipher.NewCBCEncrypter(s.c, iv)
66 mode.CryptBlocks(ciphertext[blockSize:], padtext)
67
68 return hex.EncodeToString(ciphertext)
69}
70
71func (s *Safe) Decrypt(ciphertextHex string) (string, error) {
72 ciphertext, err := hex.DecodeString(ciphertextHex)
73 if err != nil {
74 return "", err
75 }
76
77 blockSize := s.c.BlockSize()
78 if len(ciphertext)%blockSize != 0 {
79 return "", errors.New("ciphertext is not a multiple of the block size")
80 }
81
82 iv := ciphertext[:blockSize]
83 ciphertext = ciphertext[blockSize:]
84
85 mode := cipher.NewCBCDecrypter(s.c, iv)
86 mode.CryptBlocks(ciphertext, ciphertext)
87
88 // Remove padding
89 padding := int(ciphertext[len(ciphertext)-1])
90 if padding > blockSize || padding <= 0 {
91 return "", errors.New("invalid padding")
92 }
93
94 return string(ciphertext[:len(ciphertext)-padding]), nil
95}