The codebase that powers boop.cat boop.cat
at main 133 lines 2.4 kB view raw
1// Copyright 2025 boop.cat 2// Licensed under the Apache License, Version 2.0 3// See LICENSE file for details. 4 5package lib 6 7import ( 8 "crypto/aes" 9 "crypto/cipher" 10 "crypto/rand" 11 "crypto/sha256" 12 "encoding/base64" 13 "os" 14 "strings" 15 16 "golang.org/x/crypto/pbkdf2" 17) 18 19const ( 20 ivLength = 16 21 authTagLength = 16 22 saltLength = 16 23 keyLength = 32 24) 25 26func getEncryptionKey() []byte { 27 secret := os.Getenv("ENV_ENCRYPTION_SECRET") 28 if secret == "" { 29 return nil 30 } 31 32 saltHash := sha256.Sum256([]byte(secret + ":salt")) 33 salt := saltHash[:saltLength] 34 35 return pbkdf2.Key([]byte(secret), salt, 100000, keyLength, sha256.New) 36} 37 38func IsEncryptionEnabled() bool { 39 return os.Getenv("ENV_ENCRYPTION_SECRET") != "" 40} 41 42func Encrypt(plaintext string) string { 43 if plaintext == "" { 44 return plaintext 45 } 46 47 key := getEncryptionKey() 48 if key == nil { 49 50 return plaintext 51 } 52 53 block, err := aes.NewCipher(key) 54 if err != nil { 55 return plaintext 56 } 57 58 gcm, err := cipher.NewGCM(block) 59 if err != nil { 60 return plaintext 61 } 62 63 iv := make([]byte, gcm.NonceSize()) 64 if _, err := rand.Read(iv); err != nil { 65 return plaintext 66 } 67 68 ciphertext := gcm.Seal(nil, iv, []byte(plaintext), nil) 69 70 authTag := ciphertext[len(ciphertext)-authTagLength:] 71 encrypted := ciphertext[:len(ciphertext)-authTagLength] 72 73 return "enc:v1:" + 74 base64.StdEncoding.EncodeToString(iv) + ":" + 75 base64.StdEncoding.EncodeToString(authTag) + ":" + 76 base64.StdEncoding.EncodeToString(encrypted) 77} 78 79func Decrypt(ciphertext string) string { 80 if ciphertext == "" { 81 return ciphertext 82 } 83 84 if !strings.HasPrefix(ciphertext, "enc:v1:") { 85 86 return ciphertext 87 } 88 89 key := getEncryptionKey() 90 if key == nil { 91 92 return ciphertext 93 } 94 95 parts := strings.Split(ciphertext, ":") 96 if len(parts) != 5 { 97 return ciphertext 98 } 99 100 iv, err := base64.StdEncoding.DecodeString(parts[2]) 101 if err != nil { 102 return ciphertext 103 } 104 105 authTag, err := base64.StdEncoding.DecodeString(parts[3]) 106 if err != nil { 107 return ciphertext 108 } 109 110 encrypted, err := base64.StdEncoding.DecodeString(parts[4]) 111 if err != nil { 112 return ciphertext 113 } 114 115 block, err := aes.NewCipher(key) 116 if err != nil { 117 return ciphertext 118 } 119 120 gcm, err := cipher.NewGCM(block) 121 if err != nil { 122 return ciphertext 123 } 124 125 fullCiphertext := append(encrypted, authTag...) 126 127 plaintext, err := gcm.Open(nil, iv, fullCiphertext, nil) 128 if err != nil { 129 return ciphertext 130 } 131 132 return string(plaintext) 133}