1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
|
# -*- coding: utf-8 -*-
"""
# AES wrapper
# Copyright (c) 2023-2024 Michael Büsch <m@bues.ch>
# Licensed under the GNU/GPL version 2 or later.
"""
from libpwman.exception import PWManError
import os
__all__ = [
"AES",
]
class AES:
"""Abstraction layer for the AES implementation.
"""
BLOCK_SIZE = 128 // 8
__singleton = None
@classmethod
def get(cls):
"""Get the AES singleton.
"""
if cls.__singleton is None:
cls.__singleton = cls()
return cls.__singleton
def __init__(self):
self.__pyaes = None
self.__cryptodome = None
cryptolib = os.getenv("PWMAN_CRYPTOLIB", "").lower().strip()
if cryptolib in ("", "cryptodome"):
# Try to use Cryptodome
try:
import Cryptodome
import Cryptodome.Cipher.AES
import Cryptodome.Util.Padding
self.__cryptodome = Cryptodome
return
except ImportError as e:
pass
if cryptolib in ("", "pyaes"):
# Try to use pyaes
try:
import pyaes
self.__pyaes = pyaes
return
except ImportError as e:
pass
msg = "Python module import error."
if cryptolib == "":
msg += "\nNeither 'Cryptodome' nor 'pyaes' is installed."
else:
msg += "\n'PWMAN_CRYPTOLIB=%s' is not supported or not installed." % cryptolib
raise PWManError(msg)
def encrypt(self, key, iv, data):
"""Encrypt data.
"""
# Check parameters.
if len(key) != 256 // 8:
raise PWManError("AES: Invalid key length.")
if len(iv) != self.BLOCK_SIZE:
raise PWManError("AES: Invalid iv length.")
if len(data) <= 0:
raise PWManError("AES: Invalid data length.")
try:
if self.__cryptodome is not None:
# Use Cryptodome
padData = self.__cryptodome.Util.Padding.pad(
data_to_pad=data,
block_size=self.BLOCK_SIZE,
style="pkcs7")
cipher = self.__cryptodome.Cipher.AES.new(
key=key,
mode=self.__cryptodome.Cipher.AES.MODE_CBC,
iv=iv)
encData = cipher.encrypt(padData)
return encData
if self.__pyaes is not None:
# Use pyaes
mode = self.__pyaes.AESModeOfOperationCBC(key=key, iv=iv)
padding = self.__pyaes.PADDING_DEFAULT
enc = self.__pyaes.Encrypter(mode=mode, padding=padding)
encData = enc.feed(data)
encData += enc.feed()
return encData
except Exception as e:
raise PWManError("AES error: %s: %s" % (type(e), str(e)))
raise PWManError("AES not implemented.")
def decrypt(self, key, iv, data, legacyPadding=False):
"""Decrypt data.
"""
# Check parameters.
if len(key) != 256 // 8:
raise PWManError("AES: Invalid key length.")
if len(iv) != self.BLOCK_SIZE:
raise PWManError("AES: Invalid iv length.")
if len(data) <= 0:
raise PWManError("AES: Invalid data length.")
try:
if self.__cryptodome is not None:
# Use Cryptodome
cipher = self.__cryptodome.Cipher.AES.new(
key=key,
mode=self.__cryptodome.Cipher.AES.MODE_CBC,
iv=iv)
decData = cipher.decrypt(data)
if legacyPadding:
unpadData = self.__unpadLegacy(decData)
else:
unpadData = self.__cryptodome.Util.Padding.unpad(
padded_data=decData,
block_size=self.BLOCK_SIZE,
style="pkcs7")
return unpadData
if self.__pyaes is not None:
# Use pyaes
mode = self.__pyaes.AESModeOfOperationCBC(key=key, iv=iv)
if legacyPadding:
padding = self.__pyaes.PADDING_NONE
else:
padding = self.__pyaes.PADDING_DEFAULT
dec = self.__pyaes.Decrypter(mode=mode, padding=padding)
decData = dec.feed(data)
decData += dec.feed()
if legacyPadding:
unpadData = self.__unpadLegacy(decData)
else:
unpadData = decData
return unpadData
except Exception as e:
raise PWManError("AES error: %s: %s" % (type(e), str(e)))
raise PWManError("AES not implemented.")
@staticmethod
def __unpadLegacy(data):
"""Strip legacy padding.
"""
index = data.rfind(b"\xFF")
if index < 0 or index >= len(data):
raise PWManError("Legacy padding: Did not find start.")
return data[:index]
@classmethod
def quickSelfTest(cls):
inst = cls.get()
enc = inst.encrypt(key=(b"_keykey_" * 4), iv=(b"iv" * 8), data=b"pwman")
if enc != bytes.fromhex("cf73a286509e1265d26490a76dcbb2fd"):
raise PWManError("AES encrypt: Quick self test failed.")
dec = inst.decrypt(key=(b"_keykey_" * 4), iv=(b"iv" * 8), data=enc)
if dec != b"pwman":
raise PWManError("AES decrypt: Quick self test failed.")
|