age-encryption
Version:
<p align="center"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://github.com/FiloSottile/age/blob/main/logo/logo_white.svg"> <source media="(prefers-color-scheme: light)" srcset="https://github.com/FiloSottile/a
55 lines (54 loc) • 1.92 kB
JavaScript
import { base64 } from "@scure/base";
/**
* Encode an age encrypted file using the ASCII armor format, a strict subset of
* PEM that starts with `-----BEGIN AGE ENCRYPTED FILE-----`.
*
* @param file - The raw encrypted file (returned by {@link Encrypter.encrypt}).
*
* @returns The ASCII armored file, with a final newline.
*/
export function encode(file) {
const lines = [];
lines.push("-----BEGIN AGE ENCRYPTED FILE-----\n");
for (let i = 0; i < file.length; i += 48) {
let end = i + 48;
if (end > file.length)
end = file.length;
lines.push(base64.encode(file.subarray(i, end)) + "\n");
}
lines.push("-----END AGE ENCRYPTED FILE-----\n");
return lines.join("");
}
/**
* Decode an age encrypted file from the ASCII armor format, a strict subset of
* PEM that starts with `-----BEGIN AGE ENCRYPTED FILE-----`.
*
* Extra whitespace before and after the file is ignored, and newlines can be
* CRLF or LF, but otherwise the format is parsed strictly.
*
* @param file - The ASCII armored file.
*
* @returns The raw encrypted file (to be passed to {@link Decrypter.decrypt}).
*/
export function decode(file) {
const lines = file.trim().replaceAll("\r\n", "\n").split("\n");
if (lines.shift() !== "-----BEGIN AGE ENCRYPTED FILE-----") {
throw Error("invalid header");
}
if (lines.pop() !== "-----END AGE ENCRYPTED FILE-----") {
throw Error("invalid footer");
}
function isLineLengthValid(i, l) {
if (i === lines.length - 1) {
return l.length > 0 && l.length <= 64 && l.length % 4 === 0;
}
return l.length === 64;
}
if (!lines.every((l, i) => isLineLengthValid(i, l))) {
throw Error("invalid line length");
}
if (!lines.every((l) => /^[A-Za-z0-9+/=]+$/.test(l))) {
throw Error("invalid base64");
}
return base64.decode(lines.join(""));
}