On this page:
1.1 Installation
1.2 Protecting Data in Memory
1.3 Saving and Loading Encrypted Data
1.3.1 Entropy
1.4 Complete Example:   Storing Configuration
9.0

1 Getting Started🔗

This guide walks through common usage patterns for the DPAPI library.

1.1 Installation🔗

Until Racket is updated to support SHA256 commits, you will need to clone the source code and then install locally with:

git clone https://codeberg.org/joeld/racket-dpapi.git

cd racket-dpapi

raco pkg install dpapi/ dpapi-lib/

Omit dpapi/ to exclude building the local documentation.

Then require it in your Racket programs:

(require dpapi)

1.2 Protecting Data in Memory🔗

The core abstraction is the protected value, which keeps sensitive data encrypted in memory. The only way to access the plaintext is through a callback inside with-decrypted-data:

(require dpapi)
 
;; Check if DPAPI is available
(unless (dpapi-available?)
  (error "DPAPI not available on this platform"))
 
;; Create a protected value - data is encrypted immediately
(define password
  (make-protected-value (string->bytes/utf-8 "my-secret-password")))
 
;; Access the data through a callback
(with-decrypted-data password
  (lambda (pwd-bytes)
    (define pwd (bytes->string/utf-8 pwd-bytes))
    (connect-to-database pwd)))
; Password is automatically re-encrypted after the callback
 
; When done, zero out memory
(destroy-protected-value! password)

The with-decrypted-data pattern ensures that data remains unencrypted only during the time it is needed. The data gets re-encrypted automatically once the callback is complete, even if the callback throws an exception.

1.3 Saving and Loading Encrypted Data🔗

A protected value is not suitable for writing to permanent storage, because it is generally scoped only to the currently running process.

The export-protected-bytes and import-protected-bytes functions convert between in-memory protected values and DPAPI-encrypted values that can be used across sessions, without exposing the plaintext to the rest of your code:

(require dpapi)
 
;; Create a protected value
(define token
  (make-protected-value (string->bytes/utf-8 token-from-oauth)))
 
;; Export to DPAPI-encrypted bytes and save to disk
(define encrypted-bytes
  (export-protected-bytes token
                          #:entropy (string->bytes/utf-8 "app-v1-secret")
                          #:description "OAuth Token"))
 
(with-output-to-file "token.encrypted"
  (lambda () (write-bytes encrypted-bytes))
  #:exists 'replace)
 
;; --- Later: load from disk ---
 
(define loaded-token
  (import-protected-bytes (file->bytes "token.encrypted")
                          #:entropy (string->bytes/utf-8 "app-v1-secret")))
 
;; Use the token through a callback
(with-decrypted-data loaded-token
  (lambda (token-bytes)
    (make-api-request (bytes->string/utf-8 token-bytes))))

To retrieve the description stored with the encrypted data:

(define loaded-token
  (import-protected-bytes (file->bytes "token.encrypted")
                          #:entropy (string->bytes/utf-8 "app-v1-secret")))
 
(protected-value-description loaded-token)
;; => "OAuth Token"

1.3.1 Entropy🔗

The optional #:entropy parameter on export-protected-bytes and import-protected-bytes adds a secondary secret. Without the correct entropy, decryption will fail even for the same Windows user. The same entropy must be provided for both export and import.

; Export with entropy
(define encrypted
  (export-protected-bytes pv
                          #:entropy (string->bytes/utf-8 "my-app-secret")))
 
;; Import must use the same entropy
(define pv2
  (import-protected-bytes encrypted
                          #:entropy (string->bytes/utf-8 "my-app-secret")))
 
;; Wrong entropy will raise exn:fail:dpapi

1.4 Complete Example: Storing Configuration🔗

Here’s a complete example for storing encrypted configuration:

(require dpapi json)
 
;; Configuration to encrypt
(define config
  (hasheq 'database-password "super-secret"
          'api-key "key-12345"))
 
;; Serialize, protect, and export
(define config-json (jsexpr->string config))
(define config-pv
  (make-protected-value (string->bytes/utf-8 config-json)))
 
(define encrypted-config
  (export-protected-bytes config-pv
                          #:description "App Configuration"
                          #:entropy (string->bytes/utf-8 "app-v1-secret")))
 
;; Save to file
(with-output-to-file "config.encrypted"
  (lambda () (write-bytes encrypted-config))
  #:exists 'replace)
 
(destroy-protected-value! config-pv)
 
;; --- Later: Load and use ---
 
;; Import from file
(define loaded-config-pv
  (import-protected-bytes (file->bytes "config.encrypted")
                          #:entropy (string->bytes/utf-8 "app-v1-secret")))
 
;; Use the config through a callback
(with-decrypted-data loaded-config-pv
  (lambda (config-bytes)
    (define loaded-config
      (string->jsexpr (bytes->string/utf-8 config-bytes)))
    (displayln loaded-config)))
; => '#hasheq((database-password . "super-secret") (api-key . "key-12345"))