PEM (Privacy-Enhanced Mail) — текстовый формат представления криптографических ключей и сертификатов. В контексте защиты приватных ключей используется механизм симметричного шифрования, при котором содержимое PEM-блока зашифровано с использованием пароля.
Зашифрованный PEM имеет характерную структуру:
-----BEGIN ENCRYPTED PRIVATE KEY-----
...
-----END ENCRYPTED PRIVATE KEY-----
или:
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,ABCD1234...
...
-----END RSA PRIVATE KEY-----
Ключевые особенности:
Proc-Type и DEK-Info
указывает на использование устаревшего формата PKCS#1 с симметричным
шифрованием.ENCRYPTED PRIVATE KEY соответствует PKCS#8 и
считается более современным.Библиотека Jsrsasign поддерживает оба варианта.
Основной класс для работы с ключами — KEYUTIL. Метод
getKey автоматически определяет тип ключа и при
необходимости выполняет расшифровку.
Пример загрузки:
const encryptedPEM = `-----BEGIN ENCRYPTED PRIVATE KEY-----
...
-----END ENCRYPTED PRIVATE KEY-----`;
const password = "strongpassword";
const keyObj = KEYUTIL.getKey(encryptedPEM, password);
Особенности:
RSAKey, ECDSA,
DSA и др.).Jsrsasign реализует поддержку популярных схем:
Для PKCS#1:
Для PKCS#8:
Пример строки DEK-Info:
DEK-Info: AES-256-CBC,0123456789ABCDEF
При работе с зашифрованными PEM важно учитывать возможные ошибки:
Неверный пароль
try {
KEYUTIL.getKey(pem, "wrongpass");
} catch (e) {
console.error("Ошибка расшифровки:", e);
}Повреждённый PEM
Неподдерживаемый алгоритм
После расшифровки полезно определить тип ключа:
if (keyObj instanceof RSAKey) {
console.log("RSA ключ");
} else if (keyObj.type === "EC") {
console.log("EC ключ");
}
Также доступно свойство:
console.log(keyObj.isPrivate); // true
Jsrsasign позволяет не только читать, но и создавать зашифрованные ключи.
Пример генерации RSA-ключа и его шифрования:
const kp = KEYUTIL.generateKeypair("RSA", 2048);
const encryptedPEM = KEYUTIL.getPEM(kp.prvKeyObj, "PKCS8PRV", "password123");
Параметры:
"PKCS8PRV" — формат"password123" — парольРезультат — строка PEM с шифрованием.
Можно явно задать алгоритмы:
const pem = KEYUTIL.getPEM(
kp.prvKeyObj,
"PKCS8PRV",
"password123",
{
encalg: "aes256-cbc",
iter: 10000
}
);
Параметры:
encalg — алгоритм шифрованияiter — количество итераций PBKDF2Чем выше iter, тем выше стойкость к перебору.
Частая задача — конвертация старого PKCS#1 PEM в современный PKCS#8:
const key = KEYUTIL.getKey(oldPem, password);
const newPem = KEYUTIL.getPEM(key, "PKCS8PRV", "newpassword");
Преимущества PKCS#8:
Иногда требуется получить открытый (незашифрованный) вариант:
const key = KEYUTIL.getKey(encryptedPem, password);
const plainPem = KEYUTIL.getPEM(key, "PKCS1PRV");
Результат:
Публичные ключи не шифруются паролем, но могут быть извлечены из приватного:
const key = KEYUTIL.getKey(encryptedPem, password);
const pubPem = KEYUTIL.getPEM(key, "PKCS8PUB");
Критические аспекты:
Пример:
const password = process.env.KEY_PASSWORD;
Расшифровка включает:
Факторы влияния:
const sig = new KJUR.crypto.Signature({ alg: "SHA256withRSA" });
const key = KEYUTIL.getKey(encryptedPem, password);
sig.init(key);
sig.updateString("data");
const signature = sig.sign();
Ключ загружается один раз, затем используется в криптооперациях.
const sig = new KJUR.crypto.Signature({ alg: "SHA256withRSA" });
sig.init(publicKey);
sig.updateString("data");
const isValid = sig.verify(signature);
1. “malformed PEM”
2. “unsupported algorithm”
3. “password mismatch”
Процесс включает:
Jsrsasign реализует это полностью на JavaScript без нативных зависимостей.
Перед загрузкой полезно выполнить базовую проверку:
if (!pem.includes("BEGIN")) {
throw new Error("Некорректный PEM");
}
PBKDF2 замедляет перебор паролей:
Jsrsasign полностью кроссплатформенен:
Однако:
Для ключей 4096+ бит:
Оптимизация:
let cachedKey = null;
function getKey() {
if (!cachedKey) {
cachedKey = KEYUTIL.getKey(pem, password);
}
return cachedKey;
}
Jsrsasign может использоваться совместно с:
Пример использования в JWT:
const header = { alg: "RS256", typ: "JWT" };
const payload = { sub: "123456" };
const sJWT = KJUR.jws.JWS.sign(
"RS256",
JSON.stringify(header),
JSON.stringify(payload),
KEYUTIL.getKey(encryptedPem, password)
);
Пароль интерпретируется как UTF-8 строка. Ошибки возможны при:
Если ключ в бинарном формате:
const key = KEYUTIL.getKeyFromEncryptedPKCS8PEM(pem, password);
Для DER требуется предварительное преобразование в PEM.
ASN.1 структура включает:
Jsrsasign разбирает ASN.1 через встроенные модули.