On this page:
2.1 Platform Detection
dpapi-available?
2.2 Protected Values
make-protected-value
with-decrypted-data
destroy-protected-value!
protected-value?
protected-value-description
2.3 Disk Persistence
export-protected-bytes
import-protected-bytes
2.4 Error Handling
exn:  fail:  dpapi
9.0

2 API Reference🔗

2.1 Platform Detection🔗

procedure

(dpapi-available?)  boolean?

Returns #t if DPAPI is available on the current platform, #f otherwise.

On Windows Vista or later, this returns #t. On all other platforms (Linux, macOS, etc.), it returns #f.

(if (dpapi-available?)
    (displayln "DPAPI is available")
    (displayln "DPAPI is not available"))

2.2 Protected Values🔗

A protected value is an opaque wrapper that keeps sensitive data encrypted in memory using CryptProtectMemory. The only way to access the plaintext is through with-decrypted-data, which temporarily decrypts the data for the duration of a callback and automatically re-encrypts afterward.

procedure

(make-protected-value data 
  [#:scope scope 
  #:description description]) 
  protected-value?
  data : bytes?
  scope : (or/c 'same-process 'cross-process 'same-logon)
   = 'same-process
  description : (or/c string? #f) = #f
Creates a protected value with data immediately encrypted in memory.

The data is automatically padded to 16-byte blocks and encrypted. Returns an opaque struct that can only be accessed through with-decrypted-data.

The scope parameter controls which processes can decrypt the in-memory representation:

  • 'same-process: Only this process can decrypt

  • 'cross-process: Any process on this machine can decrypt

  • 'same-logon: Any process by the same logged-in user can decrypt

The description is an optional label stored in the protected value. It is used as the default description when exporting with export-protected-bytes, and can be retrieved with protected-value-description.

Example:

(define password
  (make-protected-value (string->bytes/utf-8 "secret")
                        #:description "User Password"))

procedure

(with-decrypted-data pv proc)  any

  pv : protected-value?
  proc : (-> bytes? any)
Temporarily decrypts the bytes inside pv, calls proc with the decrypted (unpadded) data, then automatically re-encrypts before returning.

The data is only exposed during the dynamic extent of the callback. The protected value is always re-encrypted, even if proc throws an exception.

This is the only way to access the data inside a protected value.

Example:

(with-decrypted-data password
  (lambda (pwd-bytes)
    (connect-to-database (bytes->string/utf-8 pwd-bytes))))

procedure

(destroy-protected-value! pv)  void?

  pv : protected-value?
Zeros out the encrypted data and marks the protected value as destroyed. Any subsequent call to with-decrypted-data on this value will raise exn:fail:dpapi.

This is a best-effort attempt to remove sensitive data from memory. Racket’s garbage collector may have already copied the data elsewhere.

Example:

(destroy-protected-value! password)

procedure

(protected-value? v)  boolean?

  v : any/c
Returns #t if v is a protected value, #f otherwise.

procedure

(protected-value-description pv)  (or/c string? #f)

  pv : protected-value?
Returns the description associated with pv, or #f if none was provided.

When a protected value is created by import-protected-bytes, the description stored in the DPAPI blob is automatically captured here.

2.3 Disk Persistence🔗

These functions bridge between protected values (in-memory) and DPAPI-encrypted bytes suitable for writing to disk, using CryptProtectData and CryptUnprotectData internally. The plaintext is never exposed to calling code—it is decrypted and re-encrypted internally.

procedure

(export-protected-bytes pv 
  [#:description description 
  #:entropy entropy 
  #:machine-scope? machine-scope? 
  #:audit? audit?]) 
  bytes?
  pv : protected-value?
  description : (or/c string? #f) = #f
  entropy : (or/c bytes? #f) = #f
  machine-scope? : boolean? = #f
  audit? : boolean? = #f
Exports the contents of pv as DPAPI-encrypted bytes suitable for persistent storage (files, databases, etc.). The returned bytes are encrypted using Windows credentials and can be imported back with import-protected-bytes. Raises exn:fail:dpapi if DPAPI is not available, if the protected value has been destroyed, or if encryption fails.

If description is not provided, the description from pv (see protected-value-description) is used. If explicitly provided, it overrides the protected value’s description. Either way, a non-#f description is stored unencrypted alongside the encrypted data, where it will be captured by import-protected-bytes.

If entropy is not #f, it is used as a factor in the encryption of the exported bytes. An attempt to re-import the resulting bytes without identical entropy will fail with an exception.

If machine-scope? is #t, the resulting bytes will be decryptable by other users on the same machine.

If audit? is #t, Windows will generate audit log entries for the operation (but only if description is provided as a non-empty string).

Example:

(define encrypted-bytes
  (export-protected-bytes password
                          #:entropy (string->bytes/utf-8 "app-secret")
                          #:description "User Password"))
 
;; Write to disk
(with-output-to-file "password.encrypted"
  (lambda () (write-bytes encrypted-bytes))
  #:exists 'replace)

procedure

(import-protected-bytes encrypted-data    
  [#:entropy entropy    
  #:scope scope])  protected-value?
  encrypted-data : bytes?
  entropy : (or/c bytes? #f) = #f
  scope : (or/c 'same-process 'cross-process 'same-logon)
   = 'same-process
Imports DPAPI-encrypted bytes (previously created by export-protected-bytes) and returns a protected value. The plaintext is decrypted from the DPAPI format and immediately re-encrypted in memory—it is never returned directly. Raises exn:fail:dpapi if decryption fails (wrong entropy, corrupted data, wrong user/machine, etc.).

If a description was stored in the DPAPI blob, it is automatically captured in the returned protected value and can be retrieved with protected-value-description.

The entropy must match the value used during export.

The scope parameter sets the memory protection scope for the resulting protected value (see make-protected-value).

; Read from disk
(define encrypted-bytes (file->bytes "password.encrypted"))
 
; Import into a protected value
(define password
  (import-protected-bytes encrypted-bytes
                          #:entropy (string->bytes/utf-8 "app-secret")))
 
; Retrieve the description
(protected-value-description password)

2.4 Error Handling🔗

struct

(struct exn:fail:dpapi (message
    continuation-marks
    error-code
    error-message)
    #:transparent)
  message : string?
  continuation-marks : continuation-mark-set?
  error-code : exact-nonnegative-integer?
  error-message : string?
Exception structure for DPAPI errors. Extends exn:fail.

  • error-code: Windows error code from GetLastError

  • error-message: Human-readable description of the error

The message field contains both context and the error message.

Error Code

  

Description

13

  

ERROR_INVALID_DATA: Data is corrupted or invalid

14

  

ERROR_OUTOFMEMORY: Not enough memory

50

  

ERROR_NOT_SUPPORTED: Operation not supported on this OS

87

  

ERROR_INVALID_PARAMETER: Invalid parameter

0x80090003

  

NTE_BAD_KEY: Wrong entropy or protection scope

0x80090005

  

NTE_BAD_DATA: Invalid encrypted data format