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.
(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
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)
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?
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) = #f 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 |