Cardano-cli signing a transaction without directly accessing the .skey file in plain text

I am wanting to do the following:

I am looking for a way to sign a tx with the least possible risk.
I have the .skey file and I can sign tx normally.

The problem is that I don’t want to have the file in plain text because it can be somehow taken by someone who can access the computer.
If I encrypt the file and decrypt it to be able to use it, in the same way I have a few seconds (or mili, its the same) where the file is created in plain text, therefore dangerous.

I would like to be able to decrypt the file in a variable and from there I can access it or work with cardano-cli. I still can’t because the cardano-cli has the .skey FILE as an argument for the signature.
How could it be done so that all the sensitive information is encrypted, then only in memory, and after being used, encrypted again. Without ever leaving sensitive information out of memory.

Thanks!

1 Like

Have you tried process substitution? Something like this (gpg example)…

command -f <((echo $MY_PASSWORD | gpg -d --batch --yes --passphrase-fd 0 --output - ./skey.txt.gpg 2>/dev/null))
1 Like

You could decrypt the file into a location that is only a ramdisk. For example, on my machines /tmp is configured to be only in RAM.

But other processes could still access the file, while it is there. You could remove it immediately to reduce that risk.

Or you could specify a FIFO for the key file:

We use this technique to construct one of our pool keys on a server where that key would be insecure to store itself, e.g.:

mkfifo $myVRFkeyPath || (echo ${myVRFkeyPath}: cannot create FIFO; exit 1)
echo "::::::: NOW put the VRF key here: $myVRFkeyPath ::::::: "
cncli leaderlog --pool-vrf-skey "$myVRFkeyPath" ...

While this command is running the cleartext of that private key only exists in kernel memory space, and after that perhaps in some unused kernel buffer on the free list until it’s recycled… we declared that to be secure enough for our purposes. :face_with_monocle:

1 Like

I tried something similar without encryption to test a substitution process. $(cat payment.skey) , if that worked, I would do the encryption process with gpg, but it doesn’t work. It gives me a read error because it expects to open a file. In the process you provide, it only remains in memory and works with cardano-cli?

I already tried something similar and I don’t like it because I was able to access the contents of the file generated in /tmp outside the program. Therefore, as you say, it is not a safe solution for me.

Of course, in this case only the sensitive information would be housed in a variable in memory, that’s what I’m looking for. But… the problem is that cardano-cli is not allowing me to pass a non-file reference to the --signing-key-file

1 Like

I think I found the solution, if instead of $ place < it allows me to pass a reference to the internal data of the file housed in a variable in memory. I’ll test it a little better, but I think it could be the solution

is not the same as <(cat payment.skey) (see message I wrote)

Have you actually tried what @COSDpool suggested or just assuming?

1 Like

Hmmm, your’re right… though in a standards compliant UNIX design for cardano-cli there should be no way for it to know (and no reason to care) that it is reading from a named pipe instead of a file, but here it is:

$ cardano-cli transaction build \
--mainnet \
--tx-in $(cat payment.utxo) \
--tx-out $(cat wallet.addr)+1000000 \
--change-address $(cat payment.addr) \
--out-file tx.draft
Estimated transaction fee: Lovelace 168185

$ mkfifo /tmp/sign-through-fifo.skey

$ cardano-cli transaction sign \
--tx-body-file tx.draft  \
--signing-key-file /tmp/sign-through-fifo.skey \
--mainnet \
--out-file tx.signed
Command failed: transaction sign  Error: Error reading signing key: /tmp/sign-through-fifo.skey: Invalid key.

$ ls -l /tmp/sign-through-fifo.skey
prw-rw-r-- 1 spo spo 0 2022-06-26 18:45 /tmp/sign-through-fifo.skey

The proper behaviour would be for the cardano-cli transaction sign to block until it receives an EOF from reading /tmp/sign-through-fifo.skey (after you’ve written your key file contents into that file from another shell).

Now of course I just realised cncli is coming from different developers with a different “security” standard. Personally I would disagree with whatever design decision has forced private keys into flat files when they could be secured inside a kernel managed resource like a named pipe (so you’d have to be root as well as highly skilled, with expert timing, in order to read the cleartext).

@HeptaSean what do you think… is there any point submitting an issue about this?

2 Likes

If I put it like this in bash it works correctly. Obviously I have generated a file here, but the idea is to replace it with a variable in memory. The issue is that when I do the same thing from a python thread, it already stops working. I am studying the solution

`

cardano-cli transaction sign --tx-body-file tx.raw --signing-key-file <(cat payment-0.skey) --mainnet --out-file tx.signed

`

Does the suggested approach fit, where you have the key encrypted in a file and you do the decryption in memory as suggested above. Like instad of <(cat...) you do the decrypt part there. You could have a bash script that asks you for the password and then does the signing part.

As I put it above, this example is not encrypted, because I am trying to make it work in a simple way (without encryption) and then do it with the encrypted file. I already have the script that does the decryption for me, but there’s no point in applying it until I get this simpler part working first.

Part of my python code:

command_string = [‘cardano-cli’, ‘transaction’, ‘sign’, ‘–tx-body-file’, ‘tx.raw’, ‘–signing-key-file’]
command_string.append(‘<(cat payment-0.skey)’)
command_string.append(‘–mainnet’)
command_string.append(‘–out-file’)
command_string.append(‘tx.signed’)
print(command_string)
proceso = subprocess.run(command_string,capture_output=True,text=True)

Ok I see now, python is the problem. I guess a bash script is your next best bet.

Yes, the problem I’m having is that subprocess passes me the content as a literal and cardano-cli takes it as the file to open. If I directly pass a variable with the content of the signature file, cardano cli does not accept it because it is looking to open a file. I am making a wallet to manage by cli with cardano-cli and python, this would be the security step that I would need to solve.

oh well, I thought I would strike at this because it may be the best long term solution to the originally reported problem… and because I’m feeling submitty today :nerd_face:

3 Likes

Seems reasonable. I cannot answer your question if it will find open ears, though. I’m not IOG.

Python does not know the <(some command) syntax, yes. But you could probably build the same functionality in Python. After all, <(some command) in bash just opens a file descriptor, puts the output of the command in it, and returns the name of that file descriptor.

But it does not really solve the original problem, since other processes might access that file also in the mean time. It is not that much different than the solution with the temporary file in a ramdisk security-wise.

The named pipe solution might be a little better (though other processes might also access that).

In the end, we cannot change that cardano-cli expects a filename, there. You could try a feature request to allow to give the signing key not by file, but in a more secure way. “More secure” would have to be defined. If it is in a variable, it will appear on the command line and could be read by other processes with a simple ps. …

It starts to look like you will want to program the transaction building and signing yourself in Python or any other programming language. Then, you can fully control that the decrypted key is only in memory and never exposed to other processes.

1 Like

Beyond the situation for which this thread starts, I consult you. By creating that fifo in a temporary file in /tmp , wouldn’t the sensitive information in plain text also be accessible by someone?

Beyond the fifo, I tried creating a temporary file and although it works as an idea and is “deleted” only at the end of the call by the program, while the program that generates it is in use, the information in the temporary files is accessible

It starts to look like you will want to program the transaction building and signing yourself in Python or any other programming language. Then, you can fully control that the decrypted key is only in memory and never exposed to other processes.

That’s the idea. A similar system already had it running in another blockchain. The idea (how it worked in the other script I made, and how I intend it to work here is…)
Having the private key encrypted with gpg, inside my python program, decrypt it and the decrypted content, instead of passing it to a file or printing it to the console, pass it directly to a program variable. Which would only be accessible in memory and by the same program. There would be no record anywhere and the signature could be advanced in a protected manner