-
-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add ASCII Armored Option [Feature Request] #45
Comments
I think so -- Patches are welcome for this 🙂 |
I'm playing a bit with pysimplegui to do a GUI based on pyrage, and armor feature would be nice to have a possibility to encrypt a multiline form. |
I may or may not try my hand at this after finishing up #56. |
For fun, I implemented this in Python. It works, and I tried my best to ensure that it is strictly RFC-compliant and everything, but I am no expert so feel free to point out any problems in my implementation. This is the relevant portion of the module I wrote: @dataclass
class Armored:
"""RFC-compliant ASCII Armor implementation for age encryption."""
PEM_HEADER = '-----BEGIN AGE ENCRYPTED FILE-----'
PEM_FOOTER = '-----END AGE ENCRYPTED FILE-----'
PEM_RE = re.compile(
rf'^{PEM_HEADER}\n' r'([A-Za-z0-9+/=\n]+)' rf'\n{PEM_FOOTER}$',
)
B64_LINE_RE = re.compile(
r'(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})?'
)
@property
def armored_data(self) -> str:
return self._armored_data
@property
def dearmored_data(self) -> bytes:
return self._dearmored_data
def __init__(self, data: bytes | str) -> None:
if isinstance(data, bytes):
self._armored_data = self._armor(data)
self._dearmored_data = self._dearmor(self._armored_data)
elif isinstance(data, str):
self._dearmored_data = self._dearmor(data)
self._armored_data = self._armor(self._dearmored_data)
else:
raise TypeError
def _decode_b64_strict(self, b64_data: str) -> bytes:
while '\r\n' in b64_data:
b64_data = b64_data.replace('\r\n', '\n')
while '\r' in b64_data:
b64_data = b64_data.replace('\r', '\n')
b64_lines = b64_data.split('\n')
for idx, line in enumerate(b64_lines):
if idx < len(b64_lines) - 1:
if len(line) != 64:
raise ValueError(f'Line {idx+1} length is not 64 characters.')
elif len(line) > 64:
raise ValueError('Final line length exceeds 64 characters.')
b64_str = ''.join(b64_lines)
if not re.fullmatch(self.B64_LINE_RE, b64_str):
raise ValueError('Invalid Base64 encoding detected.')
try:
decoded_data = binascii.a2b_base64(b64_str, strict_mode=True)
except binascii.Error as exc:
raise ValueError('Base64 decoding error: ' + str(exc)) from exc
return decoded_data
def _armor(self, data: bytes) -> str:
b64_encoded = binascii.b2a_base64(data, newline=False).decode('ascii')
b64_lines = [b64_encoded[i : i + 64] for i in range(0, len(b64_encoded), 64)]
return '\n'.join([self.PEM_HEADER, *b64_lines, self.PEM_FOOTER])
def _dearmor(self, pem_data: str) -> bytes:
pem_data = pem_data.strip()
match = re.fullmatch(self.PEM_RE, pem_data)
if not match:
raise ValueError('Invalid PEM format or extra data found.')
b64_data = match.group(1)
return self._decode_b64_strict(b64_data) Figured I'd share this here in case it was helpful to anyone else. |
Hi, I couldn't find a way to generate an ascii armored output with the encrypt function. Is this something that could be added easily ?
Thanks for the great package!
The text was updated successfully, but these errors were encountered: