Protecting Memory Secrets: Difference between revisions

From Rosetta Code
Content added Content deleted
(How can your language protect in-memory secrets from eavesdropping)
 
(Added Wren)
Line 71: Line 71:


See also [https://www.reddit.com/r/ProgrammingLanguages/comments/100tyxg/secrets_management_in_volatile_memory_best/ Reddit discussion of the issue]
See also [https://www.reddit.com/r/ProgrammingLanguages/comments/100tyxg/secrets_management_in_volatile_memory_best/ Reddit discussion of the issue]

=={{header|Wren}}==
{{libheader|Wren-crypto}}
Wren is designed to be an embedded scripting language - 'embedded' in the sense that it is embedded in another application, not used for scripting an embedded device though the latter may be possible if the device has sufficient memory available.

As such Wren code is effectively sandboxed and can only communicate with external processes (even just printing to the terminal) to the extent that the host allows it to do so.

It is written in C (as is the embedding API) and consequently most host applications are written in C/C++ though other powerful languages with a C FFI such as Rust or Go can be used instead.

Wren itself can be thought of as a sort of mini-Java in that it is deeply object oriented, managed by its VM, garbage collected and strings are immutable.

Given this state of affairs, I think in practice most host application developers would conclude that any secret should be handled entirely by the host where it can be cleaned up after use by zeroing memory in the usual fashion or, if it does need to be shared with Wren code, then it should be passed as a (possibly encrypted) list of bytes which the Wren code would need to zero out before it was eventually garbage collected.

Wren also has a tool (Wren-CLI) for running Wren scripts directly without a host which is the main focus for solving RC tasks. In reality, there is still a host (written in C) which uses the cross-platform library 'libuv' to implement IO functionality (both terminal and file) and provides Wren with the appropriate modules, though this is transparent to the user.

The tool is a work in progress and would need considerable hardening to handle secrets in a secure fashion. Perhaps the nearest one could get to the task as described would be the following though note that, to display anything to the terminal, Wren first converts it to a string and as these are immutable there is no way to remove them from memory before the GC frees them. To avoid this, we therefore display the bytes entered after encryption by SHA-256 so that only the hash will remain in memory.
<syntaxhighlight lang="ecmascript">import "io" for Stdin
import "./crypto" for Sha256

// Ensure input is not echoed or buffered
Stdin.isRaw = true

// Obtain secret from terminal input as a byte list
System.print("Enter a secret word and press return:")
var bytes = []
while (true) {
var b = Stdin.readByte()
if (b == 13) break
bytes.add(b)
}

// Encrypt bytes and display on terminal
System.print(Sha256.digest(bytes))

// Zero out bytes
for (i in 0...bytes.count) bytes[i] = 0

// Check it worked
System.print(bytes)

// Make byte list eligible for GC
bytes = null

// Restore normal input before exit
Stdin.isRaw = false</syntaxhighlight>

{{out}}
This assumes the secret word entered is 'abc'.
<pre>
Enter a secret word and press return:
ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
[0, 0, 0]
</pre>

Revision as of 10:54, 13 January 2023

Protecting Memory Secrets is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

The object of the task is to show how to minimize the exposure of secret data, basically to remove it or render it unrecoverable at a point in time such as a specific event (e.g. authorization of a transaction, completion of a transaction, completion of a process, removal of plain text after encryption, etc.

Some of you may find this task a bit unusual in that it doesn't demonstrate a specific algorithm, derive a specific output, or demonstrate a visible functionality. It's about how you can meet a security requirement in your language. It's also a security requirement that hasn't been explicitly thought through in the design of many languages (Hint: it is entirely possible that it may not be possible in all languages). There isn't going to be a single right answer or solution and it isn't about translating from another language. Even similar types of languages or even different implementations of the same language could have dissimilar solutions. The idea is to show how your language can protect secrets in memory from all comers!

Why this task? It reflects the growing need to better protect secrets as well as changes in best practices being driven by security standards.

The point is to help developers by providing patterns that they could actually use to use to achieve these objectives.

For the purposes of this task:

- secrets: information like credit card or social insurance numbers, passwords, cryptographic keys or IV's, random number seeds, etc. - threats: things like memory scrapers that may not even be written in the same language - don't assume the only threat is code written in your language

The object of the task is to show how to minimize the exposure of secret data, basically to remove it or render it unrecoverable at a point in time such as a specific event (e.g. authorization of a transaction, completion of a transaction, completion of a process, removal of plain text after encryption, etc.

Task

The basic task is:

  • read a secret into memory
  • write or display the secret
  • securely erase or destroy the secret
  • In all cases you will want to be careful of temporary variables, system calls, and other things that could leave plain-text artifacts.

How you accomplish this will depend on your language and your knowledge of its memory management:

In all of these variations take care not to contaminate temporary variables, the stack, etc. with function calls or conversions.

1. Unmanaged Memory (programmer managed)

In languages like C, assembly, etc. this can be as simple as zeroing all of the data after use and before freeing. Take care not to contaminate temporary variables, the stack, etc.

2. Managed Memory (garbage collectors)

In languages like Java and .Net which manage memory for the programmer this can be challenging. Many of these represent some types of data (e.g. strings) as immutable objects so zeroing them isn't possible. Simply discarding them and waiting for the garbage collector to possibly sort things out doesn't meet the intent. If your language has a type for secrets or a guaranteed destructors that would work this is exactly what this task is for.

a) If possible switch to a mutable data type. In some languages strings are immutable but arrays of single characters or even numbers are not and can be "zeroed" out.

b) If you can't switch to mutable type, there may be techniques to constrain the data and force a local memory clean up.

c) Data obfuscation techniques may be possible.

d) In-memory encryption may be possible using a language or platform feature (No DIY).

e) Call out to a platform function or API that will contain and manage the secret.

f) Call out to a custom external function that will could maintain and manage the secret (just show an external call, no need to write the external function).

g) Something completely different that achieves the same goal.

Part of your solution will be describe what you are doing and the "secret sauce" that lets it work (i.e. how you overcame challenges). References (links, book references) to supporting langauge features, APIs, etc. will be important to any developer needing to do use this technique to comply with a standard.


References

If anyone has examples of similar regulations or standards please add them below or on the talk page.

PCI Point-to-Point-Encryption (P2PE) Standard (v3.1) PCI P2PE Standard see requirements 2A-2.3 & 2B-1.5

  • has two types of secrets called PAN and SAD
  • don't secrets in working memory any longer than strictly necessary
  • developers should have secure coding traing for their langauge that includes managing sensitive data in memory

PCI Secure Software Standard (v1.2) PCI Secure Software Standard see requirements 1.1 & 3.5

  • has a broader definition of secrets or sensitive information
  • implement methods to render transient sensitive data irretrievable and to confirm that sensitive data is unrecoverable after the process is complete even if it is only stored temporarily in program memory / variables during operation of the software
  • requires knowledge of any platform or implementation level issues that complicate the OF erasure transient sensitive data and to confirm that methods have been implemented to minimize the risk posed by these complications (methods may be external to your language).

See also Reddit discussion of the issue

Wren

Library: Wren-crypto

Wren is designed to be an embedded scripting language - 'embedded' in the sense that it is embedded in another application, not used for scripting an embedded device though the latter may be possible if the device has sufficient memory available.

As such Wren code is effectively sandboxed and can only communicate with external processes (even just printing to the terminal) to the extent that the host allows it to do so.

It is written in C (as is the embedding API) and consequently most host applications are written in C/C++ though other powerful languages with a C FFI such as Rust or Go can be used instead.

Wren itself can be thought of as a sort of mini-Java in that it is deeply object oriented, managed by its VM, garbage collected and strings are immutable.

Given this state of affairs, I think in practice most host application developers would conclude that any secret should be handled entirely by the host where it can be cleaned up after use by zeroing memory in the usual fashion or, if it does need to be shared with Wren code, then it should be passed as a (possibly encrypted) list of bytes which the Wren code would need to zero out before it was eventually garbage collected.

Wren also has a tool (Wren-CLI) for running Wren scripts directly without a host which is the main focus for solving RC tasks. In reality, there is still a host (written in C) which uses the cross-platform library 'libuv' to implement IO functionality (both terminal and file) and provides Wren with the appropriate modules, though this is transparent to the user.

The tool is a work in progress and would need considerable hardening to handle secrets in a secure fashion. Perhaps the nearest one could get to the task as described would be the following though note that, to display anything to the terminal, Wren first converts it to a string and as these are immutable there is no way to remove them from memory before the GC frees them. To avoid this, we therefore display the bytes entered after encryption by SHA-256 so that only the hash will remain in memory.

import "io" for Stdin
import "./crypto" for Sha256

// Ensure input is not echoed or buffered
Stdin.isRaw = true

// Obtain secret from terminal input as a byte list
System.print("Enter a secret word and press return:")
var bytes = []
while (true) {
    var b = Stdin.readByte()
    if (b == 13) break
    bytes.add(b)
}

// Encrypt bytes and display on terminal
System.print(Sha256.digest(bytes))

// Zero out bytes
for (i in 0...bytes.count) bytes[i] = 0

// Check it worked
System.print(bytes)

// Make byte list eligible for GC
bytes = null

// Restore normal input before exit
Stdin.isRaw = false
Output:

This assumes the secret word entered is 'abc'.

Enter a secret word and press return:
ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
[0, 0, 0]