#url-safe #primary-key #string #id #encoded-string #integer #decryption

cryptid-rs

A library to encrypt and decrypt integer IDs to URL safe strings

3 releases

new 0.1.2 Dec 6, 2024
0.1.1 Jul 11, 2024
0.1.0 Jun 11, 2024

#506 in Cryptography

MIT/Apache

30KB
460 lines

Cryptid-rs

Cryptid-rs is a library for securely encoding and decoding integers, such as database primary keys, into strings and back. The encoded string format is inspired by Stripe's APIs.

For example, a user ID of 123 might be encoded into user_hHLBCl4rZ3u.

Benefits

  • Type-safe: Encoded strings carry a prefix, preventing accidental confusion of IDs of different types.
  • Short: Compared to UUIDs, cryptid strings are short by default: 11 characters plus a type prefix.
  • Works with integer database keys: If you use integers as your database primary keys but don't want to expose them directly in your API, this library is suitable.
  • Unguessable: The cryptids are encrypted, making them not only obfuscated but also unguessable.
  • Serde and Diesel support: Define generic types that work directly with Serde and Diesel to transparently encode and decode identifiers as they go in and out of your API or database layer.

Some of these benefits may also be disadvantages depending on your needs. Consider carefully if this library is the right choice for you compared to other solutions, such as using UUIDs as your database keys.

Example

Use the generic Field type to define a type for each kind of object you are exposing in your public APIs. The Field type supports automatic encoding and decoding with Diesel and Serde.

use cryptid_rs_rs;
use serde::{Serialize, Deserialize};
use serde_json;

// Define the ExampleId cryptid field type.  The type marker defines the string prefix.
#[derive(Debug)]
pub struct ExampleIdMarker;
impl cryptid_rs::TypeMarker for ExampleIdMarker {
    fn name() -> &'static str { "example" }
}

type ExampleId = cryptid_rs::Field<ExampleIdMarker>;

// The field can then be used in structs, and works automatically with Serde and Diesel.
#[derive(serde::Serialize)]
struct Example {
    pub id: ExampleId,
}

cryptid_rs::Config::set_global(cryptid_rs::Config::new(b"your-secure-key"));
let obj = Example {id: ExampleId::new(12345)};
let obj_str = serde_json::to_string(&obj).unwrap();
assert_eq!(obj_str, "{\"id\":\"example_VgwPy6rwatl\"}");

What's the encryption?

The encryption uses format-preserving encryption (FPE) with AES (FF1 with AES256) and HMAC (SHA256) for integrity checks.

The HMAC is truncated to 4 bytes by default, which is large enough to make guessing impractical through a rate-limited API but still keeps the strings relatively short. For high-security applications, consider using a longer HMAC.

Dependencies

~5.5MB
~106K SLoC