NAME

File::SOPS - Perl implementation of Mozilla SOPS encrypted file format

VERSION

version 0.001

SYNOPSIS

use File::SOPS;

# Encrypt a data structure
my $encrypted = File::SOPS->encrypt(
    data       => {
        database => {
            password => 'secret123',
            host     => 'db.example.com',
        },
    },
    recipients => ['age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p'],
    format     => 'yaml',
);

# Decrypt
my $data = File::SOPS->decrypt(
    encrypted  => $encrypted,
    identities => ['AGE-SECRET-KEY-1...'],
);

# File operations
File::SOPS->encrypt_file(
    input      => 'secrets.yaml',
    output     => 'secrets.enc.yaml',
    recipients => ['age1...'],
);

File::SOPS->decrypt_file(
    input      => 'secrets.enc.yaml',
    output     => 'secrets.yaml',
    identities => ['AGE-SECRET-KEY-1...'],
);

# Extract single value
my $password = File::SOPS->extract(
    file       => 'secrets.enc.yaml',
    path       => '["database"]["password"]',
    identities => ['AGE-SECRET-KEY-1...'],
);

# Rotate data key
File::SOPS->rotate(
    file       => 'secrets.enc.yaml',
    identities => ['AGE-SECRET-KEY-1...'],
);

DESCRIPTION

File::SOPS is a pure Perl implementation of Mozilla SOPS (Secrets OPerationS), compatible with the reference Go implementation at https://github.com/getsops/sops.

SOPS encrypts values in structured files (YAML, JSON) while keeping keys readable. This enables:

  • Git-friendly diffs - see which keys changed without decrypting

  • Partial file inspection without full decryption

  • Multiple encryption backends (currently age, with PGP/KMS planned)

  • MAC verification to detect tampering

How SOPS Works

1. Generate a random 256-bit data key
2. Encrypt the data key for each recipient using age (X25519 + ChaCha20-Poly1305)
3. Store encrypted data keys in the sops metadata section
4. Encrypt each value with AES-256-GCM using the data key
5. Compute MAC over the entire structure for tamper detection

Encrypted Value Format

Each encrypted value is stored as:

ENC[AES256_GCM,data:base64==,iv:base64==,tag:base64==,type:str]

File Structure Example

database:
    password: ENC[AES256_GCM,data:xyz,iv:abc,tag:def,type:str]
    host: ENC[AES256_GCM,data:xyz,iv:abc,tag:def,type:str]
sops:
    age:
        - recipient: age1ql3z7hjy...
          enc: |
            -----BEGIN AGE ENCRYPTED FILE-----
            <encrypted data key>
            -----END AGE ENCRYPTED FILE-----
    lastmodified: "2025-01-10T12:00:00Z"
    mac: ENC[AES256_GCM,data:...,iv:...,tag:...,type:str]
    version: 3.7.3

Special Features

  • unencrypted_suffix - Keys ending with _unencrypted are not encrypted but included in MAC

  • Key rotation - Re-encrypt all values with a new data key via "rotate"

  • Multiple recipients - Encrypt once, multiple recipients can decrypt

encrypt

my $encrypted = File::SOPS->encrypt(
    data       => \%data,
    recipients => \@age_public_keys,
    format     => 'yaml',  # or 'json', defaults to 'yaml'
);

Encrypts a data structure for specified recipients.

Takes a HashRef in data, encrypts all values (not keys) using AES-256-GCM, and encrypts the data key for each age recipient. Returns serialized encrypted content as a string.

The recipients parameter must be an ArrayRef of age public keys (starting with age1...).

Supported formats: yaml, yml, json.

decrypt

my $data = File::SOPS->decrypt(
    encrypted  => $encrypted_content,
    identities => \@age_secret_keys,
    format     => 'yaml',  # optional, auto-detected
);

Decrypts SOPS-encrypted content.

Takes encrypted content as a string, decrypts the data key using provided age identities, verifies the MAC, and returns the decrypted data structure as a HashRef.

The identities parameter must be an ArrayRef of age secret keys (starting with AGE-SECRET-KEY-1...).

If format is not specified, it will be auto-detected from the content.

Dies if MAC verification fails or if none of the provided identities can decrypt the data key.

encrypt_file

File::SOPS->encrypt_file(
    input      => 'secrets.yaml',
    output     => 'secrets.enc.yaml',  # optional, defaults to input (in-place)
    recipients => \@age_public_keys,
    format     => 'yaml',              # optional, auto-detected from filename
);

Encrypts a file.

Reads the input file, encrypts it for the specified recipients, and writes the encrypted content to the output file. If output is not specified, encrypts in-place (overwrites the input file).

Format is auto-detected from the filename extension (.yaml, .yml, .json) unless explicitly specified.

Returns true on success.

decrypt_file

File::SOPS->decrypt_file(
    input      => 'secrets.enc.yaml',
    output     => 'secrets.yaml',
    identities => \@age_secret_keys,
    format     => 'yaml',  # optional, auto-detected from filename
);

Decrypts a SOPS-encrypted file.

Reads the encrypted input file, decrypts it using the provided identities, and writes the decrypted content to the output file.

Unlike "encrypt_file", output is required to prevent accidental data loss.

Returns true on success.

extract

my $value = File::SOPS->extract(
    file       => 'secrets.enc.yaml',
    path       => '["database"]["password"]',
    identities => \@age_secret_keys,
    format     => 'yaml',  # optional, auto-detected from filename
);

Extracts and decrypts a single value from an encrypted file.

This is more efficient than decrypting the entire file when you only need one value.

Path can be specified in two formats:

  • Bracket notation: ["database"]["password"]

  • Dot notation: database.password

For array indices, use numeric keys: ["items"][0] or items.0

Returns the decrypted value (scalar, not reference).

rotate

File::SOPS->rotate(
    file       => 'secrets.enc.yaml',
    identities => \@age_secret_keys,
    recipients => \@new_recipients,  # optional, keeps current recipients
    format     => 'yaml',            # optional, auto-detected from filename
);

Rotates the data key (re-encrypts all values with a new key).

This operation:

1. Decrypts the file using identities
2. Generates a new random data key
3. Re-encrypts all values with the new data key
4. Encrypts the new data key for recipients (or existing recipients if not specified)
5. Writes back to the same file

Key rotation is recommended periodically for security, or when removing a recipient's access.

Returns true on success.

SEE ALSO

SUPPORT

IRC

You can reach Getty on irc.perl.org for questions and support.

CONTRIBUTING

Contributions are welcome! Please fork the repository and submit a pull request.

AUTHOR

Torsten Raudssus <torsten@raudssus.de>

COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Torsten Raudssus.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.