2 API Reference
2.1 Platform Detection
procedure
On Windows Vista or later, this returns #t. On all other platforms (Linux, macOS, etc.), it returns #f.
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
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.
The contents of data are copied into the protected value; the original byte string is neither modified nor zeroed, since it may be immutable. See Practical Guidance for how to handle the original.
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)
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. Raises exn:fail if pv has been destroyed with destroy-protected-value!.
Access to a protected value is exclusive and not re-entrant. While proc is running, other threads that call with-decrypted-data, export-protected-bytes, or destroy-protected-value! on the same value block until proc returns. Calling any of those on the same value from within proc on the same thread raises exn:fail rather than deadlocking.
If the thread running proc is terminated with kill-thread, the cleanup that re-encrypts and zeros the data does not run. The plaintext remains in memory, and the value stays locked, so any later call on it blocks indefinitely (a blocked caller can still be interrupted with break-thread). Use break-thread rather than kill-thread to interrupt a callback; breaks unwind normally and the value is re-encrypted.
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?
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
procedure
(protected-value-description pv) → (or/c string? #f)
pv : protected-value?
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—
procedure
(export-protected-bytes pv [ #:description description #:entropy entropy #:machine-scope? machine-scope? #:audit? audit?]) → bytes? pv : protected-value?
description : (or/c string? #f) = (protected-value-description pv) entropy : (or/c bytes? #f) = #f machine-scope? : boolean? = #f audit? : boolean? = #f
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
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?
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 |