|
| 1 | +package crypto |
| 2 | + |
| 3 | +import ( |
| 4 | + "bytes" |
| 5 | + "crypto/aes" |
| 6 | + "crypto/cipher" |
| 7 | + "encoding/hex" |
| 8 | + "fmt" |
| 9 | + "strings" |
| 10 | + |
| 11 | + lua "github.com/yuin/gopher-lua" |
| 12 | +) |
| 13 | + |
| 14 | +type mode uint |
| 15 | + |
| 16 | +const ( |
| 17 | + GCM mode = iota + 1 |
| 18 | + CBC |
| 19 | + CTR |
| 20 | +) |
| 21 | + |
| 22 | +var modeNames = map[string]mode{ |
| 23 | + "GCM": GCM, |
| 24 | + "CBC": CBC, |
| 25 | + "CTR": CTR, |
| 26 | +} |
| 27 | + |
| 28 | +func (m mode) String() string { |
| 29 | + switch m { |
| 30 | + case GCM: |
| 31 | + return "GCM" |
| 32 | + case CBC: |
| 33 | + return "CBC" |
| 34 | + case CTR: |
| 35 | + return "CTR" |
| 36 | + default: |
| 37 | + return "unknown" |
| 38 | + } |
| 39 | +} |
| 40 | + |
| 41 | +func parseString(s string) (mode, error) { |
| 42 | + ret, ok := modeNames[strings.ToUpper(s)] |
| 43 | + if !ok { |
| 44 | + return 0, fmt.Errorf("invalid mode: %s", s) |
| 45 | + } |
| 46 | + return ret, nil |
| 47 | +} |
| 48 | + |
| 49 | +func decodeParams(l *lua.LState) (m mode, key, iv, data []byte, err error) { |
| 50 | + modeString := l.ToString(1) |
| 51 | + m, err = parseString(modeString) |
| 52 | + if err != nil { |
| 53 | + return 0, nil, nil, nil, err |
| 54 | + } |
| 55 | + |
| 56 | + keyStr := l.ToString(2) |
| 57 | + key, err = hex.DecodeString(keyStr) |
| 58 | + if err != nil { |
| 59 | + return 0, nil, nil, nil, fmt.Errorf("failed to decode key: %v", err) |
| 60 | + } |
| 61 | + |
| 62 | + ivStr := l.ToString(3) |
| 63 | + iv, err = hex.DecodeString(ivStr) |
| 64 | + if err != nil { |
| 65 | + return 0, nil, nil, nil, fmt.Errorf("failed to decode IV: %v", err) |
| 66 | + } |
| 67 | + |
| 68 | + dataStr := l.ToString(4) |
| 69 | + data, err = hex.DecodeString(dataStr) |
| 70 | + if err != nil { |
| 71 | + return 0, nil, nil, nil, fmt.Errorf("failed to decode data: %v", err) |
| 72 | + } |
| 73 | + return m, key, iv, data, nil |
| 74 | +} |
| 75 | + |
| 76 | +// encryptAES implements AES encryption given mode, key, plaintext, and init value. |
| 77 | +// Init value is either initialization vector or nonce, depending on the mode. |
| 78 | +func encryptAES(m mode, key, init, plaintext []byte) ([]byte, error) { |
| 79 | + block, err := aes.NewCipher(key) |
| 80 | + if err != nil { |
| 81 | + return nil, err |
| 82 | + } |
| 83 | + switch m { |
| 84 | + case GCM: |
| 85 | + aesGCM, err := cipher.NewGCM(block) |
| 86 | + if err != nil { |
| 87 | + return nil, err |
| 88 | + } |
| 89 | + if len(init) != aesGCM.NonceSize() { |
| 90 | + return nil, fmt.Errorf("incorrect GCM nonce size: %d, expected: %d", len(init), aesGCM.NonceSize()) |
| 91 | + } |
| 92 | + ciphertext := aesGCM.Seal(nil, init, plaintext, nil) |
| 93 | + return ciphertext, nil |
| 94 | + case CBC: |
| 95 | + if len(init) != block.BlockSize() { |
| 96 | + return nil, fmt.Errorf("invalid IV size: %d, expected: %d", len(init), block.BlockSize()) |
| 97 | + } |
| 98 | + padded := pad(plaintext, aes.BlockSize) |
| 99 | + mode := cipher.NewCBCEncrypter(block, init) |
| 100 | + ciphertext := make([]byte, len(padded)) |
| 101 | + mode.CryptBlocks(ciphertext, padded) |
| 102 | + return ciphertext, nil |
| 103 | + case CTR: |
| 104 | + if len(init) != block.BlockSize() { |
| 105 | + return nil, fmt.Errorf("invalid IV size: %d, expected: %d", len(init), block.BlockSize()) |
| 106 | + } |
| 107 | + stream := cipher.NewCTR(block, init) |
| 108 | + ciphertext := make([]byte, len(plaintext)) |
| 109 | + stream.XORKeyStream(ciphertext, plaintext) |
| 110 | + return ciphertext, nil |
| 111 | + default: |
| 112 | + return nil, fmt.Errorf("unsupported mode: %d", m) |
| 113 | + } |
| 114 | +} |
| 115 | + |
| 116 | +// decryptAES implements AES decryption given mode, key, ciphertext, and init value. |
| 117 | +// Init value is either initialization vector or nonce, depending on the mode. |
| 118 | +func decryptAES(m mode, key, init, ciphertext []byte) ([]byte, error) { |
| 119 | + block, err := aes.NewCipher(key) |
| 120 | + if err != nil { |
| 121 | + return nil, err |
| 122 | + } |
| 123 | + switch m { |
| 124 | + case GCM: |
| 125 | + aesGCM, err := cipher.NewGCM(block) |
| 126 | + if err != nil { |
| 127 | + return nil, err |
| 128 | + } |
| 129 | + l := len(init) |
| 130 | + if l != aesGCM.NonceSize() { |
| 131 | + return nil, fmt.Errorf("incorrect GCM nonce size: %d, expected: %d", len(init), aesGCM.NonceSize()) |
| 132 | + } |
| 133 | + plaintext, err := aesGCM.Open(nil, init, ciphertext, nil) |
| 134 | + if err != nil { |
| 135 | + return nil, err |
| 136 | + } |
| 137 | + return plaintext, nil |
| 138 | + case CBC: |
| 139 | + if len(ciphertext)%aes.BlockSize != 0 { |
| 140 | + return nil, fmt.Errorf("ciphertext is not a multiple of block size") |
| 141 | + } |
| 142 | + mode := cipher.NewCBCDecrypter(block, init) |
| 143 | + plaintext := make([]byte, len(ciphertext)) |
| 144 | + mode.CryptBlocks(plaintext, ciphertext) |
| 145 | + // Padding reversal is intentionally delegated to the application layer. |
| 146 | + // On constrained devices with fixed-length payloads, padding is sometimes omitted |
| 147 | + // to avoid unnecessary processing load and data overhead. |
| 148 | + return plaintext, nil |
| 149 | + case CTR: |
| 150 | + stream := cipher.NewCTR(block, init) |
| 151 | + plaintext := make([]byte, len(ciphertext)) |
| 152 | + stream.XORKeyStream(plaintext, ciphertext) |
| 153 | + return plaintext, nil |
| 154 | + default: |
| 155 | + return nil, fmt.Errorf("unsupported mode: %s", m) |
| 156 | + } |
| 157 | +} |
| 158 | + |
| 159 | +func pad(data []byte, blockSize int) []byte { |
| 160 | + padLen := blockSize - len(data)%blockSize |
| 161 | + padding := bytes.Repeat([]byte{byte(padLen)}, padLen) |
| 162 | + return append(data, padding...) |
| 163 | +} |
0 commit comments