Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Crypto Encoding Laravel Package

sop/crypto-encoding

A Laravel/PHP package for crypto encoding and decoding, providing convenient helpers to convert cryptographic values between formats. It integrates cleanly into Laravel applications for working with encoded keys, hashes, and encrypted payloads, with simple APIs that keep common encoding tasks readab...

Deep Wiki
Context7

Getting Started

Install with Composer:

composer require sop/crypto-encoding

The package lives under the Sop\CryptoEncoding namespace and is intentionally small. Start by reading src/PEM.php — everything else (PEM\Certificate, PEM\PrivateKey, PEM\PublicKey, PEM\CertificationRequest, Base64Url, SPKI, PKCS8) is a thin specialization around that core class. Understanding PEM::fromFile(), PEM::fromString(), type(), der(), and data() gets you 90% of the way through the library.

The first real use case is the one you'll hit repeatedly: load a PEM file, check what kind of object it is, and hand the raw DER bytes to whatever downstream consumer you have.

use Sop\CryptoEncoding\PEM;

$pem = PEM::fromFile('/etc/ssl/certs/app.pem');

if ($pem->type() === 'CERTIFICATE') {
    $der = $pem->der();                 // raw binary DER
    $fingerprint = hash('sha256', $der);
    // ...
}

That's the whole mental model: this package decodes the wrapper, validates the envelope, and gives you back either the structured object (for known types) or the DER payload (for anything else). It is a parsing library, not a crypto library — it does not sign, encrypt, or validate anything.

Implementation Patterns

Dispatch on type before doing anything else. PEM files are used for certificates, CSRs, several flavors of private key (RSA PRIVATE KEY, PRIVATE KEY, EC PRIVATE KEY, ENCRYPTED PRIVATE KEY), public keys (PUBLIC KEY, RSA PUBLIC KEY), and assorted non-crypto blobs. Never assume a .pem file contains what its extension suggests. Read type() first and branch, otherwise you'll blow up downstream when someone drops a CSR into a folder marked "certs".

Separate parsing from consumption. A clean pattern is a small factory in your app that maps a PEM to a domain object:

function loadCredential(string $path): Credential
{
    $pem = PEM::fromFile($path);

    return match ($pem->type()) {
        'CERTIFICATE'          => new CertificateCredential($pem),
        'PRIVATE KEY'          => new PrivateKeyCredential($pem),
        'PUBLIC KEY'           => new PublicKeyCredential($pem),
        'CERTIFICATE REQUEST'  => new CsrCredential($pem),
        default                => throw new InvalidArgumentException(
            "Unsupported PEM type: {$pem->type()}"
        ),
    };
}

The value objects should hold the original PEM object and expose der() for consumers that want bytes, and toString() for consumers (like openssl_x509_read, openssl_pkey_get_private, or a shelled-out sop binary) that insist on PEM text.

Bridge to OpenSSL, don't replace it. The package doesn't do X.509 parsing beyond the envelope. If you need subject, issuer, SANs, or expiry, extract the DER and feed it to openssl_x509_parse after reconstructing PEM, or to a dedicated parser. The typical glue is:

$info = openssl_x509_parse($pem->toString());
$expires = $info['validTo_time_t'];

For keys, openssl_pkey_get_private($pem->toString()) works for both PKCS#1 and PKCS#8 envelopes, which is a nice reason not to re-implement that dispatch yourself.

Round-trip for normalization. A useful day-to-day trick: read a PEM, call toString(), and write it back. That normalizes line endings, strips trailing junk, and re-wraps the base64 consistently. If a vendor ships a file with mixed endings or extra headers, normalizing through the library is a five-line fix that is easier to audit than a regex.

Handle PEM bundles explicitly. Certificate chains are a single file with multiple -----BEGIN CERTIFICATE----- blocks. PEM::fromString() parses one block. Split first, then loop:

preg_match_all(
    '/-----BEGIN [^-]+-----.*?-----END [^-]+-----/s',
    $bundle,
    $matches
);

$certs = array_map(
    fn (string $block) => PEM::fromString($block),
    $matches[0]
);

That regex is intentionally loose so you can split mixed bundles (cert + key); after splitting, use type() to route each block.

Use the package as the seam in tests. Because it produces deterministic DER, it's a good fixture tool. Generate a key/cert with OpenSSL once, commit the PEM, and in tests load it via PEM::fromFile() to get both the wrapper and the DER without shelling out. For fingerprinting and serial-number comparisons, comparing DER SHA-256 hex strings is more robust than comparing PEM text (which can differ in wrapping even for identical keys).

Encrypt/decrypt and signing belong elsewhere. The package will happily give you ENCRYPTED PRIVATE KEY DER, but it will not decrypt it. Pass it to openssl_pkey_get_private($pem, $password).

Gotchas and Tips

  • CRLF vs LF matters. OpenSSL on Windows writes \r\n inside the base64 body and a trailing \r\n after the footer. The parser is generally tolerant, but if you build your own splitter or serializer, use PHP_EOL-agnostic logic. When constructing PEM strings yourself, always end with a newline after the closing -----END ...----- — some consumers (OpenSSL's CLI, older Java) fail silently without it.

  • Bundles are not parsed as bundles. PEM::fromString() expects one block. Feed it a two-block file and you get the first block (or an exception depending on version). Splitting is on you. This is the single most common source of "why does my chain have one cert?" bugs.

  • type() is the literal header, not a semantic type. RSA PRIVATE KEY (PKCS#1) and PRIVATE KEY (PKCS#8) are different strings for functionally equivalent key material. Don't branch on PRIVATE KEY alone; treat the family as a set. Similarly EC PRIVATE KEY vs PRIVATE KEY for EC keys.

  • Encrypted PEMs are still PEM. Legacy encrypted keys carry Proc-Type: 4,ENCRYPTED and DEK-Info: lines between the header and the base64. Modern ones use a ENCRYPTED PRIVATE KEY type instead. The package will parse both envelopes, but it won't decrypt either. Route encrypted material to OpenSSL or phpseclib.

  • Base64 decoding is strict. Whitespace inside a line, missing padding, or stray characters will throw. If you're ingesting PEMs from email bodies, log files, or env vars, sanitize first: preg_replace('/\s+/', '', $body) on the payload between header and footer, then reconstruct. Note that \s includes the line breaks you need to strip anyway.

  • DER is binary. Don't put der() output into JSON, logs, or HTML without base64-encoding first. A common debug mistake is var_dump($pem->der()) producing a wall of garbage and a truncated terminal.

  • Don't mutate the object. These are value objects — der() and data() return copies or immutable strings. If you need a different representation, build a new PEM rather than reaching inside.

  • Exceptions are the API contract. Malformed input throws (typically RuntimeException or a package-specific exception). Wrap ingestion at your boundary — file read, API upload, config bootstrap — and translate into your own exception type so callers don't couple to Sop\CryptoEncoding internals. That also simplifies swapping the library out later.

  • Extension point is subclassing. If you support a proprietary PEM type (e.g. a custom SOP PUBLIC KEY block), subclass PEM, override type() and any construction helpers, and register it in whatever factory you write. There's no plugin registry; dispatch is on you.

  • Version pinning is worth it. At 16 stars this is a small, single-maintainer package. Pin to a caret constraint you've tested, and keep the usage surface narrow (parse PEM → get DER or type). The narrower your direct calls, the cheaper an eventual replacement is, and there's a real chance you swap it for a openssl_*-only path or phpseclib if activity stalls.

  • Testing tip: exercise three fixtures at minimum — a PKCS#8 key with LF endings, an X.509 cert with CRLF endings, and a two-cert chain. That trio catches the line-ending, type-dispatch, and bundle-splitting bugs that account for most production incidents with this class of library.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource