About PEM Validator
PEM is the text wrapper almost all keys and certificates travel in: a BEGIN line, Base64 of the underlying DER bytes wrapped at 64 characters, and a matching END line. This tool checks that structure and tells you what the label actually says the contents are - which is usually the question you have.
The label matters more than people expect, because it identifies the encoding format and libraries are picky about it. '-----BEGIN RSA PRIVATE KEY-----' is PKCS#1, holding an RSA key directly. '-----BEGIN PRIVATE KEY-----' is PKCS#8, a generic wrapper that names the algorithm inside and works for RSA, EC and Ed25519 alike. '-----BEGIN PUBLIC KEY-----' is SPKI. '-----BEGIN CERTIFICATE-----' is an X.509 certificate, not a key at all - a common confusion when a deployment expects one and gets the other.
Which format you need depends on the consumer. Web Crypto accepts only PKCS#8 for private keys and SPKI for public ones. Java wants PKCS#8. OpenSSL reads all of them. Converting is a single command and never requires regenerating the key, which is worth knowing before someone rotates a certificate unnecessarily.
The most common breakage has nothing to do with cryptography: it is whitespace. A PEM stored in an environment variable or a JSON config frequently arrives with literal backslash-n sequences instead of real newlines, or with the line breaks stripped entirely. Parsers require the header and footer on their own lines and generally expect the Base64 wrapped at 64 characters, so a single-line PEM fails to parse with an error that says nothing about newlines.
Validation is structural and runs in your browser - the file is not uploaded, and a private key pasted here is not transmitted.
How to use the PEM Validator
- Paste the whole PEM block, including the BEGIN and END lines.
- Read what the label says it is - certificate, PKCS#1 key, PKCS#8 key, or SPKI public key.
- If parsing fails, check the newlines first: literal \n sequences and stripped line breaks are the usual cause.
- Convert to the format your library wants with the openssl commands below rather than generating a new key.
PEM Validator in code
The same operation this tool performs, in the languages you are most likely to need it.
# What is actually in this file?
openssl asn1parse -in file.pem | head
# Certificates
openssl x509 -in cert.pem -noout -text
openssl x509 -in cert.pem -noout -subject -issuer -dates
# Keys
openssl rsa -in key.pem -check -noout # RSA sanity check
openssl pkey -in key.pem -noout -text # any key type
# Convert PKCS#1 -> PKCS#8 (what Web Crypto and Java need)
openssl pkcs8 -topk8 -nocrypt -in pkcs1.pem -out pkcs8.pem
# Extract the public key from a private key
openssl pkey -in private.pem -pubout -out public.pem
# Does this key match this certificate? The two hashes must be equal.
openssl x509 -noout -modulus -in cert.pem | openssl sha256
openssl rsa -noout -modulus -in key.pem | openssl sha256
# A PEM from an env var with literal \n instead of real newlines
echo "$PRIVATE_KEY" | sed 's/\\n/\n/g' > key.pem
# In Node
const pem = process.env.PRIVATE_KEY.replace(/\\n/g, "\n");
# In Python
pem = os.environ["PRIVATE_KEY"].replace("\\n", "\n")
# Better: base64 the whole PEM into the env var and decode it,
# which sidesteps newline handling entirely
echo "$PRIVATE_KEY_B64" | base64 -d > key.pem
# Check what you actually have
head -1 key.pem # should be a -----BEGIN line
wc -l key.pem # 1 line means the newlines are gone
file key.pem
// Web Crypto takes DER bytes, not PEM - strip and decode first.
function pemToDer(pem) {
const b64 = pem
.replace(/-----BEGIN [^-]+-----/, "")
.replace(/-----END [^-]+-----/, "")
.replace(/\s+/g, "");
return Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
}
// Private keys MUST be PKCS#8 ("BEGIN PRIVATE KEY").
// A PKCS#1 key ("BEGIN RSA PRIVATE KEY") will be rejected -
// convert it with: openssl pkcs8 -topk8 -nocrypt
const key = await crypto.subtle.importKey(
"pkcs8", pemToDer(pem),
{ name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, false, ["sign"]
);
// Public keys use "spki" ("BEGIN PUBLIC KEY").
When you need this
- Working out whether a file is a certificate, a private key or a public key.
- Checking whether a key is PKCS#1 or PKCS#8 before a library rejects it.
- Diagnosing a PEM that will not parse after passing through an environment variable.
- Confirming a private key matches the certificate it is deployed with.
- Verifying a certificate's subject and expiry date.
Common problems and what causes them
- Literal \n instead of real newlines
- A PEM stored in an environment variable or JSON config often arrives with backslash-n as two characters. Parsers need real line breaks and report an unrelated-sounding error. Replace them, or Base64 the whole PEM into the variable and decode it.
- PKCS#1 supplied where PKCS#8 is required
- "BEGIN RSA PRIVATE KEY" is PKCS#1; Web Crypto and Java need PKCS#8 ("BEGIN PRIVATE KEY"). Convert with openssl pkcs8 -topk8 -nocrypt. Do not regenerate the key - the material is the same.
- Certificate mistaken for a key
- "BEGIN CERTIFICATE" is an X.509 certificate containing a public key and metadata, not a private key. A deployment that wants a key and is handed a certificate fails in a way that rarely says so clearly.
- Missing intermediate certificates
- A server needs its leaf certificate followed by any intermediates in one file, leaf first. A chain that validates in your browser (which caches intermediates) can fail for other clients that do not.
- An encrypted private key
- "Proc-Type: 4,ENCRYPTED" or a PKCS#8 encrypted header means the key needs a passphrase. Libraries that do not prompt for one simply fail to parse it. Decrypt with openssl pkey -in enc.pem -out plain.pem.
- Base64 line length
- PEM conventionally wraps the Base64 at 64 characters. Most parsers tolerate other widths, but some strict ones do not - and a PEM with no line breaks at all fails almost everywhere.
FAQ
- What is the difference between PKCS#1 and PKCS#8?
- PKCS#1 ("BEGIN RSA PRIVATE KEY") holds an RSA key directly. PKCS#8 ("BEGIN PRIVATE KEY") is a generic container that names the algorithm inside, so it works for RSA, EC and Ed25519. Web Crypto and Java require PKCS#8; converting is one openssl command.
- Why won't my PEM parse?
- Newlines, nine times out of ten - literal \n sequences from an environment variable, or line breaks stripped entirely. After that: the wrong format for the library (PKCS#1 versus PKCS#8), or an encrypted key with no passphrase supplied.
- How do I check whether a key matches a certificate?
- Compare the modulus hashes: openssl x509 -noout -modulus -in cert.pem | openssl sha256 against openssl rsa -noout -modulus -in key.pem | openssl sha256. Identical output means they are a pair.
- Is a certificate the same as a public key?
- No. A certificate contains a public key plus a subject, issuer, validity dates and a signature from a certificate authority. You can extract the public key from a certificate, but not the reverse.
- Is it safe to paste a private key here?
- The validation is structural and runs entirely in your browser, with nothing transmitted. Even so, the better habit is never to paste a production private key into a web page - use openssl locally for real keys.
Related reading
- RSA key pair generator
- Base64 converter the PEM body