GithubHelp home page GithubHelp logo

sagecipher's Introduction

sagecipher

PyPI Codecov Build Status

sagecipher (ssh agent cipher) provides an AES cipher, whose key is obtained by signing nonce data via SSH agent. This is illustrated below.

Cipher illustration

This can be used in turn by the keyring library, and by ansible-vault to encrypt/decrypt files or secrets via the users' local or forwarded ssh-agent session.

Contents

Installation

pip install sagecipher

Usage

Before using, ssh-agent must be running with at least one ssh-key available for producing cipher key material:

$ source <(ssh-agent)
Agent pid 3710

$ ssh-add
Enter passphrase for /home/somebody/.ssh/id_rsa:
Identity added: /home/somebody/.ssh/id_rsa (/home/somebody/.ssh/id_rsa)

Using the keyring backend

Here we will set the following environment variables:

Environment Variable Value Description
PYTHON_KEYRING_BACKEND sagecipher.keyring.Keyring Tells keyring explicitly to use the sagecipher backend
KEYRING_PROPERTY_SSH_KEY_FINGERPRINT <hex fingerprint of ssh key> Pre-selects the SSH key for the sagecipher backend to use

If no other keyring backends are available, sagecipher will be selected as the default backend with a priority of 1. The PYTHON_KEYRING_BACKEND environment variable can be set to explicitly set the backend. See the keyring docs for more help using the keyring library.

$ sagecipher list-keys  # paramiko does not yet expose key comments, unfortunately..
[ssh-rsa] e8:19:fe:c5:0a:b4:57:5d:96:27:b3:e3:ec:ba:24:3c
[ssh-rsa] 38:c5:94:45:ca:01:65:d1:d0:c5:ee:5e:cd:b3:94:39

$ export PYTHON_KEYRING_BACKEND=sagecipher.keyring.Keyring

$ keyring set svc user1
Password for 'user' in 'svc': 
Please select from the following keys...
[1] ssh-rsa e8:19:fe:c5:0a:b4:57:5d:96:27:b3:e3:ec:ba:24:3c
[2] ssh-rsa 38:c5:94:45:ca:01:65:d1:d0:c5:ee:5e:cd:b3:94:39
Selection (1..2): 1

$ keyring get svc user1
password1

$ export KEYRING_PROPERTY_SSH_KEY_FINGERPRINT=e8:19:fe:c5:0a:b4:57:5d:96:27:b3:e3:ec:ba:24:3c

$ keyring get svc user2
password2

$ python
Python 3.6.8 (default, Jan 14 2019, 11:02:34) 
[GCC 8.0.1 20180414 (experimental) [trunk revision 259383]] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import keyring
>>> keyring.get_password('svc', 'user1')
'password1'
>>> keyring.get_password('svc', 'user2')
'password2'

Using with ansible-vault

In this example we create a secret key in the keyring for use with ansible-vault. This process will work with any keyring backend, but it's assumed we are up and running with the sagecipher keyring backend per the previous section.

For more information, see: https://docs.ansible.com/ansible/latest/user_guide/vault.html

  1. Set up environment variables

    Environment Variable Value Description
    PYTHON_KEYRING_BACKEND sagecipher.keyring.Keyring Tells keyring to use the sagecipher backend
    KEYRING_PROPERTY_SSH_KEY_FINGERPRINT <hex fingerprint of ssh key> Pre-selects the SSH key for the sagecipher backend to use
    ANSIBLE_VAULT_PASSWORD_FILE <path to password script> ansible-vault will use this script to find the vault encryption key

    Replace the key fingerprint below with your own.

    export PYTHON_KEYRING_BACKEND=sagecipher.keyring.Keyring
    export KEYRING_PROPERTY_SSH_KEY_FINGERPRINT=e8:19:fe:c5:0a:b4:57:5d:96:27:b3:e3:ec:ba:24:3c
    export ANSIBLE_VAULT_PASSWORD_FILE=~/vault-pass.sh
  2. Generate a random key for ansible-vault and store in the keyring

    keyring set ansible-vault key < <(dd if=/dev/urandom bs=32 count=1 | base64)
  3. Create the vault password script to retrieve the vault key

    $ cat <<EOF > ~/vault-pass.sh
    #!/bin/sh
    keyring get ansible-vault key
    EOF
    
    $ chmod +x vault-pass.sh
  4. Test it out with ansible-vault

    $ ansible-vault encrypt_string "secret_password" --name "secret_attribute" > secrets.yml
    $ ansible localhost -m debug -a var="secret_attribute" -e "@secrets.yml"
    
    [WARNING]: No inventory was parsed, only implicit localhost is available
    localhost | SUCCESS => {
        "secret_attribute": "secret_password"
    }

Using sagecipher directly in Python

>>> from sagecipher import Cipher
>>>
>>> # Encrypts using the first SSH key available from SSH agent...
>>> enc_text = Cipher.encrypt_string("hello, world")
>>> text = Cipher.decrypt_string(enc_text)
>>> text
"hello, world"

Using the sagecipher CLI tool

Check sagecipher --help for usage. By default, the 'decrypt' operation will create a FIFO file, and then start a loop to decrypt out to the FIFO whenever it is opened.

The FIFO is created with mode 600 by default, and if the permissions are altered or the parent shell is terminated then the sagecipher background session will end.

$ sagecipher encrypt - encfile
Please select from the following keys...
[1] ssh-rsa e8:19:fe:c5:0a:b4:57:5d:96:27:b3:e3:ec:ba:24:3c
[2] ssh-rsa 38:c5:94:45:ca:01:65:d1:d0:c5:ee:5e:cd:b3:94:39
Selection (1..2): 1
Reading from STDIN...

secret sauce
(CTRL-D)

$ sagecipher decrypt encfile
secret sauce
$ mkfifo decfile
$ sagecipher decrypt encfile decfile &
[1] 16753
$ cat decfile  # decfile is just a FIFO
secret sauce
$

Disclaimer

Use of sagecipher

This tool is to be used at your own risk.

  • Sagecipher is only intended as a deterrant, and to reduce the risk of accidental password leaks.

  • Password authentication should be considered fundamentally broken to begin with: an attacker with privileged access to the same machine where password authentication is taking place is likely able to intercept your credentials even if these are stored in an HSM. Please consider stronger technologies to lock-down the authentication between systems and applications.

  • Implementations of signature generation functions are not necessarily stable, or deterministic. Depending on the type of SSH key and the ssh-agent implementation used, you may find that your encrypted secrets are not recoverable due to the use of non-deterministic signatures, or that you are unable to decrypt secrets after upgrading to a different version of ssh-agent.

Use of ssh-agent

The use of sagecipher depends on the use ssh-agent and it is important to be aware of the risks here too.

  • In the event a system is compromised where your ssh-agent session is exposed, an attacker will be able to use your session to decrypt your sagecipher-encrypted data and/or authenticate SSH sessions to other devices using your credentials.

  • If you use ssh-agent forwarding, this increases the exposure of your ssh-agent session to every host you connect to in this manner. As an alternative to ssh-agent forwarding, please consider SSH tunelling instead in order to reduce the attack surface of ssh-agent.

sagecipher's People

Contributors

gzm55 avatar p-sherratt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

sagecipher's Issues

sagecipher keyring.errors.Keyring Locked: Failed to unlock the collection!

If the .ssh directory is deleted and re-generated, accessing keys generates the following error:

sagecipher keyring.errors.Keyring Locked: Failed to unlock the collection!

It would seem the encrypted keys are stored somewhere on the filesystem - where does sagecipher store these so the application can choose to erase them and start over?

tip of "Reading from STDIN..." pollute the stdout result

the tip of "Reading from STDIN..." is written to stdout, which cause the stdout result cannot be decrypted correctly. when write the tip to stderr, the pipeline result can be decrypted:

echo secret | sagecipher encrypt | sagecipher decrypt

KEYRING_PROPERTY_SSH_KEY_FINGERPRINT doesn't work with multiple identities in ssh-add

Python 3.6.9
keyring Version: 22.0.1
sagecipher, version 0.7.5

keyring set ansible-vault ${USER} < <(echo "super")
Please select from the following keys...
[1] ssh-rsa ...
[2] ssh-rsa ...
Selection (1..2): Traceback (most recent call last):
  File "/home/ubuntu/.local/bin/keyring", line 8, in <module>
    sys.exit(main())
  File "/home/ubuntu/.local/lib/python3.6/site-packages/keyring/cli.py", line 135, in main
    return cli.run(argv)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/keyring/cli.py", line 68, in run
    return method()
  File "/home/ubuntu/.local/lib/python3.6/site-packages/keyring/cli.py", line 85, in do_set
    set_password(self.service, self.username, password)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/keyring/core.py", line 60, in set_password
    get_keyring().set_password(service_name, username, password)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/keyring/backends/chainer.py", line 58, in set_password
    return keyring.set_password(service, username, password)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/keyrings/alt/file_base.py", line 123, in set_password
    password_encrypted = self.encrypt(password.encode('utf-8'), assoc)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/sagecipher/keyring.py", line 33, in encrypt
    fingerprint = self.ssh_key_fingerprint
  File "/home/ubuntu/.local/lib/python3.6/site-packages/sagecipher/keyring.py", line 24, in ssh_key_fingerprint
    return prompt_for_key()
  File "/home/ubuntu/.local/lib/python3.6/site-packages/sagecipher/cipher.py", line 125, in prompt_for_key
    i = int(input("Selection (1..%s): " % len(keys)))
EOFError: EOF when reading a line

Looking at keyring.py and debgging at line 22, we see the following:

(Pdb++) pprint(getmembers(self))
[('__abstractmethods__', frozenset()),
 ('__class__', <class 'sagecipher.keyring.Keyring'>),
 ('__delattr__',
  <method-wrapper '__delattr__' of Keyring object at 0x7fe1163a67b8>),
 ('__dict__', {}),
 ('__dir__', <built-in method __dir__ of Keyring object at 0x7fe1163a67b8>),
 ('__doc__', None),
 ('__eq__', <method-wrapper '__eq__' of Keyring object at 0x7fe1163a67b8>),
 ('__format__',
  <built-in method __format__ of Keyring object at 0x7fe1163a67b8>),
 ('__ge__', <method-wrapper '__ge__' of Keyring object at 0x7fe1163a67b8>),
 ('__getattribute__',
  <method-wrapper '__getattribute__' of Keyring object at 0x7fe1163a67b8>),
 ('__gt__', <method-wrapper '__gt__' of Keyring object at 0x7fe1163a67b8>),
 ('__hash__', <method-wrapper '__hash__' of Keyring object at 0x7fe1163a67b8>),
 ('__init__', <method-wrapper '__init__' of Keyring object at 0x7fe1163a67b8>),
 ('__init_subclass__',
  <built-in method __init_subclass__ of KeyringBackendMeta object at 0x25ade38>),
 ('__le__', <method-wrapper '__le__' of Keyring object at 0x7fe1163a67b8>),
 ('__lt__', <method-wrapper '__lt__' of Keyring object at 0x7fe1163a67b8>),
 ('__module__', 'sagecipher.keyring'),
 ('__ne__', <method-wrapper '__ne__' of Keyring object at 0x7fe1163a67b8>),
 ('__new__', <built-in method __new__ of type object at 0x9d17a0>),
 ('__reduce__',
  <built-in method __reduce__ of Keyring object at 0x7fe1163a67b8>),
 ('__reduce_ex__',
  <built-in method __reduce_ex__ of Keyring object at 0x7fe1163a67b8>),
 ('__repr__',
  <bound method FileBacked.__repr__ of <Keyring with [PBKDF2] AES256.CBC (sagecipher) v.1.0 at /home/ubuntu/.local/share/python_keyring/sagecipher_pass.cfg>>),
 ('__setattr__',
  <bound method Keyring.__setattr__ of <Keyring with [PBKDF2] AES256.CBC (sagecipher) v.1.0 at /home/ubuntu/.local/share/python_keyring/sagecipher_pass.cfg>>),
 ('__sizeof__',
  <built-in method __sizeof__ of Keyring object at 0x7fe1163a67b8>),
 ('__str__',
  <bound method KeyringBackend.__str__ of <Keyring with [PBKDF2] AES256.CBC (sagecipher) v.1.0 at /home/ubuntu/.local/share/python_keyring/sagecipher_pass.cfg>>),
 ('__subclasshook__',
  <built-in method __subclasshook__ of KeyringBackendMeta object at 0x25ade38>),
 ('__weakref__', None),
 ('_abc_cache', <_weakrefset.WeakSet object at 0x7fe1163a6518>),
 ('_abc_negative_cache', <_weakrefset.WeakSet object at 0x7fe1163a6588>),
 ('_abc_negative_cache_version', 203),
 ('_abc_registry', <_weakrefset.WeakSet object at 0x7fe1163a64e0>),
 ('_classes',
  {<class 'keyring.backends.fail.Keyring'>,
   <class 'keyrings.alt.Gnome.Keyring'>,
   <class 'keyrings.alt.Google.KeyczarDocsKeyring'>,
   <class 'keyrings.alt.Google.DocsKeyring'>,
   <class 'keyrings.alt.Windows.RegistryKeyring'>,
   <class 'keyrings.alt.Windows.EncryptedKeyring'>,
   <class 'keyrings.alt.file.PlaintextKeyring'>,
   <class 'keyrings.alt.file.EncryptedKeyring'>,
   <class 'keyrings.alt.multi.MultipartKeyringWrapper'>,
   <class 'keyrings.alt.pyfs.BasicKeyring'>,
   <class 'keyrings.alt.pyfs.PlaintextKeyring'>,
   <class 'keyrings.alt.pyfs.EncryptedKeyring'>,
   <class 'keyrings.alt.pyfs.KeyczarKeyring'>,
   <class 'keyring.backends.kwallet.DBusKeyring'>,
   <class 'keyring.backends.kwallet.DBusKeyringKWallet4'>,
   <class 'keyring.backends.SecretService.Keyring'>,
   <class 'keyring.backends.Windows.WinVaultKeyring'>,
   <class 'keyring.backends.chainer.ChainerBackend'>,
   <class 'keyring.backends.macOS.Keyring'>,
   <class 'sagecipher.keyring.Keyring'>}),
 ('_ensure_file_path',
  <bound method Keyring._ensure_file_path of <Keyring with [PBKDF2] AES256.CBC (sagecipher) v.1.0 at /home/ubuntu/.local/share/python_keyring/sagecipher_pass.cfg>>),
 ('_generate_assoc',
  <bound method Keyring._generate_assoc of <Keyring with [PBKDF2] AES256.CBC (sagecipher) v.1.0 at /home/ubuntu/.local/share/python_keyring/sagecipher_pass.cfg>>),
 ('_write_config_value',
  <bound method Keyring._write_config_value of <Keyring with [PBKDF2] AES256.CBC (sagecipher) v.1.0 at /home/ubuntu/.local/share/python_keyring/sagecipher_pass.cfg>>),
 ('decrypt',
  <bound method Keyring.decrypt of <Keyring with [PBKDF2] AES256.CBC (sagecipher) v.1.0 at /home/ubuntu/.local/share/python_keyring/sagecipher_pass.cfg>>),
 ('delete_password',
  <bound method Keyring.delete_password of <Keyring with [PBKDF2] AES256.CBC (sagecipher) v.1.0 at /home/ubuntu/.local/share/python_keyring/sagecipher_pass.cfg>>),
 ('encrypt',
  <bound method Keyring.encrypt of <Keyring with [PBKDF2] AES256.CBC (sagecipher) v.1.0 at /home/ubuntu/.local/share/python_keyring/sagecipher_pass.cfg>>),
 ('file_path', '/home/ubuntu/.local/share/python_keyring/sagecipher_pass.cfg'),
 ('file_version', None),
 ('filename', 'sagecipher_pass.cfg'),
 ('get_credential',
  <bound method KeyringBackend.get_credential of <Keyring with [PBKDF2] AES256.CBC (sagecipher) v.1.0 at /home/ubuntu/.local/share/python_keyring/sagecipher_pass.cfg>>),
 ('get_password',
  <bound method Keyring.get_password of <Keyring with [PBKDF2] AES256.CBC (sagecipher) v.1.0 at /home/ubuntu/.local/share/python_keyring/sagecipher_pass.cfg>>),
 ('get_viable_backends',
  <bound method KeyringBackend.get_viable_backends of <class 'sagecipher.keyring.Keyring'>>),
 ('name', 'keyring Keyring'),
 ('priority', 1),
 ('scheme', '[PBKDF2] AES256.CBC (sagecipher)'),
 ('set_password',
  <bound method Keyring.set_password of <Keyring with [PBKDF2] AES256.CBC (sagecipher) v.1.0 at /home/ubuntu/.local/share/python_keyring/sagecipher_pass.cfg>>),
 ('set_properties_from_env',
  <bound method KeyringBackend.set_properties_from_env of <Keyring with [PBKDF2] AES256.CBC (sagecipher) v.1.0 at /home/ubuntu/.local/share/python_keyring/sagecipher_pass.cfg>>),
 ('version', '1.0'),
 ('viable', True)]

Inserting the statement on self.set_properties_from_env() line 22 fixes this issue. We then see the following:

(Pdb++) pprint(getmembers(self))
...
 ('ssh_key_fingerprint', '...'),
 ...]

It seems set_properties_from_env was added in keyring 19.3.0 -- https://pypi.org/project/keyring/19.3.0/

[Document] suggest confirming when using ssh-agent forwarding in readme

ssh-agent confirm feature could reduce a little attack surface when have to forward the ssh-agent. For a one-time password and a dedicated ssh-agent forwarded via unix domain sock file (not -A option), if we can confirm on the signing, and a special ssh-askpass helper which can delete the private key after a single success signature, it should be considered as a way of transferring the confidential via ssh with very little attack surface.

support macos

pyinotify is not avaliable on macos. when write to fifo, we could avoid depending on pyinotify, invoke write_to_fifo directly.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.