crypto.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. package cert
  2. import (
  3. "crypto/aes"
  4. "crypto/cipher"
  5. "crypto/rand"
  6. "fmt"
  7. "io"
  8. "golang.org/x/crypto/argon2"
  9. )
  10. // KDF factors
  11. type Argon2Parameters struct {
  12. version rune
  13. Memory uint32 // KiB
  14. Parallelism uint8
  15. Iterations uint32
  16. salt []byte
  17. }
  18. // Returns a new Argon2Parameters object with current version set
  19. func NewArgon2Parameters(memory uint32, parallelism uint8, iterations uint32) *Argon2Parameters {
  20. return &Argon2Parameters{
  21. version: argon2.Version,
  22. Memory: memory, // KiB
  23. Parallelism: parallelism,
  24. Iterations: iterations,
  25. }
  26. }
  27. // Encrypts data using AES-256-GCM and the Argon2id key derivation function
  28. func aes256Encrypt(passphrase []byte, kdfParams *Argon2Parameters, data []byte) ([]byte, error) {
  29. key, err := aes256DeriveKey(passphrase, kdfParams)
  30. if err != nil {
  31. return nil, err
  32. }
  33. // this should never happen, but since this dictates how our calls into the
  34. // aes package behave and could be catastraphic, let's sanity check this
  35. if len(key) != 32 {
  36. return nil, fmt.Errorf("invalid AES-256 key length (%d) - cowardly refusing to encrypt", len(key))
  37. }
  38. block, err := aes.NewCipher(key)
  39. if err != nil {
  40. return nil, err
  41. }
  42. gcm, err := cipher.NewGCM(block)
  43. if err != nil {
  44. return nil, err
  45. }
  46. nonce := make([]byte, gcm.NonceSize())
  47. if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
  48. return nil, err
  49. }
  50. ciphertext := gcm.Seal(nil, nonce, data, nil)
  51. blob := joinNonceCiphertext(nonce, ciphertext)
  52. return blob, nil
  53. }
  54. // Decrypts data using AES-256-GCM and the Argon2id key derivation function
  55. // Expects the data to include an Argon2id parameter string before the encrypted data
  56. func aes256Decrypt(passphrase []byte, kdfParams *Argon2Parameters, data []byte) ([]byte, error) {
  57. key, err := aes256DeriveKey(passphrase, kdfParams)
  58. if err != nil {
  59. return nil, err
  60. }
  61. block, err := aes.NewCipher(key)
  62. if err != nil {
  63. return nil, err
  64. }
  65. gcm, err := cipher.NewGCM(block)
  66. nonce, ciphertext, err := splitNonceCiphertext(data, gcm.NonceSize())
  67. if err != nil {
  68. return nil, err
  69. }
  70. plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
  71. if err != nil {
  72. return nil, fmt.Errorf("invalid passphrase or corrupt private key")
  73. }
  74. return plaintext, nil
  75. }
  76. func aes256DeriveKey(passphrase []byte, params *Argon2Parameters) ([]byte, error) {
  77. if params.salt == nil {
  78. params.salt = make([]byte, 32)
  79. if _, err := rand.Read(params.salt); err != nil {
  80. return nil, err
  81. }
  82. }
  83. // keySize of 32 bytes will result in AES-256 encryption
  84. key, err := deriveKey(passphrase, 32, params)
  85. if err != nil {
  86. return nil, err
  87. }
  88. return key, nil
  89. }
  90. // Derives a key from a passphrase using Argon2id
  91. func deriveKey(passphrase []byte, keySize uint32, params *Argon2Parameters) ([]byte, error) {
  92. if params.version != argon2.Version {
  93. return nil, fmt.Errorf("incompatible Argon2 version: %d", params.version)
  94. }
  95. if params.salt == nil {
  96. return nil, fmt.Errorf("salt must be set in argon2Parameters")
  97. } else if len(params.salt) < 16 {
  98. return nil, fmt.Errorf("salt must be at least 128 bits")
  99. }
  100. key := argon2.IDKey(passphrase, params.salt, params.Iterations, params.Memory, params.Parallelism, keySize)
  101. return key, nil
  102. }
  103. // Prepends nonce to ciphertext
  104. func joinNonceCiphertext(nonce []byte, ciphertext []byte) []byte {
  105. return append(nonce, ciphertext...)
  106. }
  107. // Splits nonce from ciphertext
  108. func splitNonceCiphertext(blob []byte, nonceSize int) ([]byte, []byte, error) {
  109. if len(blob) <= nonceSize {
  110. return nil, nil, fmt.Errorf("invalid ciphertext blob - blob shorter than nonce length")
  111. }
  112. return blob[:nonceSize], blob[nonceSize:], nil
  113. }