Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Changelog

All notable changes to this project are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/).

## [1.0.0] - 2026-04-22

Initial release. Stable Java API for the
[3ncr.org v1](https://3ncr.org/1/) encryption envelope (AES-256-GCM,
12-byte random IV, 16-byte GCM tag). Requires JDK 11+.

### Added

- `TokenCrypt.fromRawKey(key)` — primary constructor for callers with a
32-byte AES-256 key.
- `TokenCrypt.fromSha3(secret)` — single SHA3-256 hash for high-entropy
secrets that are not already 32 bytes (random API tokens, UUIDs, etc.).
- `TokenCrypt.fromArgon2id(secret, salt)` — Argon2id KDF for
password-strength secrets. Parameters match the
[3ncr.org v1 spec](https://3ncr.org/1/#kdf):
`m=19456 KiB, t=2, p=1`, 32-byte output, salt ≥ 16 bytes.
- `encrypt3ncr(plaintext)` / `decryptIf3ncr(value)` — encrypt to the
`3ncr.org/1#…` envelope and decrypt only values that carry the header,
passing everything else through unchanged.
- `TokenCryptException` — single (unchecked) exception class for decryption
failures (bad tag, truncated input, malformed base64).

### Notes

- Cross-verified against the canonical v1 test vectors shared with the Go,
Node.js, PHP, Python, and Rust reference implementations.
- Per 3ncr.org's new-language convention, the legacy PBKDF2-SHA3 KDF is
intentionally omitted — there is no pre-existing Java data to decrypt.
Callers needing it can derive the key with BouncyCastle's
`PKCS5S2ParametersGenerator` (using `SHA3Digest(256)`) and pass the result
to `fromRawKey`.

[1.0.0]: https://github.com/3ncr/tokencrypt-java/releases/tag/v1.0.0
73 changes: 46 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# tokencrypt (3ncr.org)

Java implementation of the [3ncr.org](https://3ncr.org/) v1 string encryption
standard.
[![Test](https://github.com/3ncr/tokencrypt-java/actions/workflows/test.yml/badge.svg)](https://github.com/3ncr/tokencrypt-java/actions/workflows/test.yml)
[![Maven Central](https://img.shields.io/maven-central/v/org._3ncr/tokencrypt.svg)](https://central.sonatype.com/artifact/org._3ncr/tokencrypt)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

3ncr.org is a small, interoperable format for encrypted strings, originally
intended for encrypting tokens in configuration files but usable for any UTF-8
string. v1 uses AES-256-GCM with a 12-byte random IV:
[3ncr.org](https://3ncr.org/) is a standard for string encryption / decryption
(algorithms + storage format), originally intended for encrypting tokens in
configuration files but usable for any UTF-8 string. v1 uses AES-256-GCM for
authenticated encryption with a 12-byte random IV:

```
3ncr.org/1#<base64(iv[12] || ciphertext || tag[16])>
Expand All @@ -14,11 +16,13 @@ string. v1 uses AES-256-GCM with a 12-byte random IV:
Encrypted values look like
`3ncr.org/1#pHRufQld0SajqjHx+FmLMcORfNQi1d674ziOPpG52hqW5+0zfJD91hjXsBsvULVtB017mEghGy3Ohj+GgQY5MQ`.

Requires JDK 11+. AES-256-GCM and SHA3-256 come from the JDK; Argon2id comes
from [Bouncy Castle](https://www.bouncycastle.org/).
This is the official Java implementation.

## Install

Requires JDK 11+. AES-256-GCM and SHA3-256 come from the JDK; Argon2id comes
from [Bouncy Castle](https://www.bouncycastle.org/).

Maven:

```xml
Expand All @@ -37,26 +41,18 @@ implementation 'org._3ncr:tokencrypt:1.0.0'

## Usage

Pick a factory based on the entropy of your secret.

### Recommended: Argon2id (low-entropy secrets)

For passwords or passphrases, use `TokenCrypt.fromArgon2id`. It uses the
parameters recommended by the [3ncr.org v1 spec](https://3ncr.org/1/#kdf)
(m=19456 KiB, t=2, p=1). Salt must be at least 16 bytes.

```java
import org._3ncr.tokencrypt.TokenCrypt;

byte[] salt = "0123456789abcdef".getBytes(StandardCharsets.UTF_8);
TokenCrypt tc = TokenCrypt.fromArgon2id("correct horse battery staple", salt);
```
Pick a factory based on the entropy of your secret — see the
[3ncr.org v1 KDF guidance](https://3ncr.org/1/#kdf) for the canonical
recommendation.

### Recommended: raw 32-byte key (high-entropy secrets)

If you already have a 32-byte AES-256 key, skip the KDF and pass it directly.

```java
import org._3ncr.tokencrypt.TokenCrypt;
import java.security.SecureRandom;

byte[] key = new byte[32];
new SecureRandom().nextBytes(key);
TokenCrypt tc = TokenCrypt.fromRawKey(key);
Expand All @@ -69,6 +65,28 @@ token), hash it through SHA3-256:
TokenCrypt tc = TokenCrypt.fromSha3("some-high-entropy-api-token");
```

### Recommended: Argon2id (passwords / low-entropy secrets)

For passwords or passphrases, use `TokenCrypt.fromArgon2id`. It uses the
parameters recommended by the [3ncr.org v1 spec](https://3ncr.org/1/#kdf)
(`m=19456 KiB, t=2, p=1`). The salt must be at least 16 bytes.

```java
import org._3ncr.tokencrypt.TokenCrypt;
import java.nio.charset.StandardCharsets;

byte[] salt = "0123456789abcdef".getBytes(StandardCharsets.UTF_8);
TokenCrypt tc = TokenCrypt.fromArgon2id("correct horse battery staple", salt);
```

### Legacy: PBKDF2-SHA3 (existing data only)

This library does not implement the legacy PBKDF2-SHA3 KDF that earlier 3ncr.org
libraries (Go, Node.js, PHP) shipped for backward compatibility. If you need to
decrypt data produced by that KDF, derive the 32-byte key with BouncyCastle's
`PKCS5S2ParametersGenerator` backed by a `SHA3Digest(256)` (or any PBKDF2-SHA3-256
implementation) and pass the result to `fromRawKey`.

### Encrypt / decrypt

```java
Expand All @@ -94,12 +112,13 @@ the [Go](https://github.com/3ncr/tokencrypt),
[Node.js](https://github.com/3ncr/nodencrypt),
[PHP](https://github.com/3ncr/tokencrypt-php),
[Python](https://github.com/3ncr/tokencrypt-python), and
[Rust](https://github.com/3ncr/tokencrypt-rust) reference libraries. The 32-byte
AES key those vectors were originally derived from (PBKDF2-SHA3-256 of
`secret = "a"`, `salt = "b"`, `iterations = 1000`) is hardcoded in the test
suite for envelope-level interop — this library only exposes the modern KDFs.
See `src/test/java/org/_3ncr/tokencrypt/TokenCryptTest.java`.
[Rust](https://github.com/3ncr/tokencrypt-rust) reference libraries. The original
32-byte AES key was derived via PBKDF2-SHA3-256 with `secret = "a"`,
`salt = "b"`, `iterations = 1000`; this library only ships the modern KDFs
(raw key / SHA3-256 / Argon2id), so the test harness hardcodes the derived key
to verify envelope-level interop. See
`src/test/java/org/_3ncr/tokencrypt/TokenCryptTest.java`.

## License

MIT
MIT — see [LICENSE](LICENSE).
21 changes: 21 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Security Policy

## Reporting a Vulnerability

Please report suspected vulnerabilities **privately** via GitHub's
[Private Vulnerability Reporting](https://github.com/3ncr/tokencrypt-java/security/advisories/new)
on the Security tab. Do not file public issues for security-sensitive reports.

We aim to acknowledge reports within 5 business days and to ship a fix or
documented mitigation within 30 days for confirmed vulnerabilities, depending
on severity and complexity.

## Supported Versions

The [3ncr.org v1 envelope](https://3ncr.org/1/) is the only supported format.
Security fixes land on the latest release of the `v1.x` line.

| Version | Supported |
| ------- | --------- |
| 1.x | Yes |
| < 1.0 | No |
Loading