crypto.go 1.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
  1. package telegram
  2. import (
  3. "crypto/aes"
  4. "crypto/cipher"
  5. "crypto/rand"
  6. "encoding/base64"
  7. "errors"
  8. "fmt"
  9. )
  10. // Crypto wraps AES-GCM with a fixed 32-byte key and random 12-byte nonces.
  11. // Used for encrypting 2FA passwords at rest.
  12. type Crypto struct {
  13. gcm cipher.AEAD
  14. }
  15. // NewCrypto builds a Crypto from a base64-encoded 32-byte key.
  16. func NewCrypto(b64Key string) (*Crypto, error) {
  17. key, err := base64.StdEncoding.DecodeString(b64Key)
  18. if err != nil {
  19. return nil, fmt.Errorf("decode secret key: %w", err)
  20. }
  21. if len(key) != 32 {
  22. return nil, fmt.Errorf("secret key must be 32 bytes, got %d", len(key))
  23. }
  24. block, err := aes.NewCipher(key)
  25. if err != nil {
  26. return nil, err
  27. }
  28. gcm, err := cipher.NewGCM(block)
  29. if err != nil {
  30. return nil, err
  31. }
  32. return &Crypto{gcm: gcm}, nil
  33. }
  34. // Encrypt returns [nonce || ciphertext || tag].
  35. func (c *Crypto) Encrypt(plain string) ([]byte, error) {
  36. nonce := make([]byte, c.gcm.NonceSize())
  37. if _, err := rand.Read(nonce); err != nil {
  38. return nil, err
  39. }
  40. return c.gcm.Seal(nonce, nonce, []byte(plain), nil), nil
  41. }
  42. // Decrypt expects the [nonce || ciphertext || tag] layout produced by Encrypt.
  43. func (c *Crypto) Decrypt(blob []byte) (string, error) {
  44. ns := c.gcm.NonceSize()
  45. if len(blob) < ns {
  46. return "", errors.New("ciphertext too short")
  47. }
  48. nonce, ct := blob[:ns], blob[ns:]
  49. pt, err := c.gcm.Open(nil, nonce, ct, nil)
  50. if err != nil {
  51. return "", err
  52. }
  53. return string(pt), nil
  54. }