Gestión de contraseñas desde la terminal

La variedad de servicios a los que debemos acceder hoy en día con una contraseña obliga a utilizar contraseñas complejas y distintas para los diferentes servicios. Para gestionarlas de la mejor manera posible existen gran variedad de gestores de contraseñas, desde aquellos que funcionan como aplicaciones de escritorio como Keepass y sus variantes, soluciones completas propietarias ofrecidas por empresas especializadas que nos proporcionan el almacenamiento remoto y acceso desde nuestro equipo a través de un complemento para el navegador o una aplicación móvil como ocurre con Lastpass y soluciones completas de código abierto que podemos utilizar de forma autohospedada o gestionada por sus creadores, como ocurre con Bitwarden.

Todas estas soluciones son muy completas y válidas para la mayor parte de los casos de uso. Pero también tienen su sitio herramientas con una base mucho más sencilla, accesibles desde todo tipo de clientes, incluso desde la terminal. Es el caso de pass, una herramienta desarrollada con la sencillez como principio y cuyo uso a través de la terminal nos permite utilizarla por ejemplo en scripts en los que sea necesaria autenticación con contraseña pero no queramos exponerlas por descuido si nuestros scripts deben estar en un repositorio público de código, entre otros muchos usos.

Pass es una aplicación desarrollada por Jason A. Donenfeld de zx2c4.com con licencia GPLv2+. También es el creador de wireguard, entre otras herramientas.

 

Contenido

 

Instalación

Se encuentra en los repositorios de las principales distribuciones de GNU/Linux, con lo que se puede instalar desde ellos o descargar el comprimido desde su repositorio de git. Se puede instalar en MacOS a través de brew.

Para Debian/Ubuntu:

sudo apt-get install -y pass

Para Fedora:

sudo dnf install -y pass

 

Modo de almacenamiento

El esquema de almacenamiento es sumamente simple:

  • Todas las contraseñas se almacenan dentro del directorio ${HOME}/.password-store.
  • Cada contraseña se almacena dentro de un archivo encriptado con gpg cuyo nombre es el nombre del sitio web o recurso cuya contraseña se almacena.
  • Estos archivos encriptados se organizan en una jerarquía de directorios para clasificarlos.

Este esquema permite que la exportación de los datos sea tan sencilla como la copia del contenido de este directorio. Simplemente tendremos que contar con la clave gpg en el lugar donde lo queramos utilizar.

 

Comando pass

Toda la gestión se realiza con el comando pass, un script de shell con funciones de autocompletado para bash, zsh y fish. Escribiendo el comando y pulsando el tabulador dos veces nos irá sugiriendo los subcomandos y parámetros que admite.

Podemos comprobar la versión actualmente instalada con el subcomando version

pass version
============================================
= pass: the standard unix password manager =
=                                          =
=                  v1.7.4                  =
=                                          =
=             Jason A. Donenfeld           =
=               Jason@zx2c4.com            =
=                                          =
=      http://www.passwordstore.org/       =
============================================

También podemos obtener ayuda con pass help.

 

Iniciar almacén

Antes de poder utilizar pass debemos iniciar el almacén de las contraseñas. Para ello existe el subcomando init al que debemos indicar el identificador de una clave GPG. Se pueden identificar múltiples claves GPG si hay un equipo de personas que debe tener acceso al almacén.

Si no disponemos de una clave gpg en nuestro equipo debemos generarla con el comando gpg --gen-key, para crear una clave con las opciones por defecto ya que sólo nos preguntará un nombre y una dirección de correo electrónico y elegirá el algoritmo y caducidad de un año, o con el comando gpg --full-generate-key, que nos permitirá especificar todas las características (algoritmo de clave, longitud de clave, caducidad, identificador, etc).

gpg --full-generate-key
gpg (GnuPG) 2.2.27; Copyright (C) 2021 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Por favor seleccione tipo de clave deseado:
   (1) RSA y RSA (por defecto)
   (2) DSA y ElGamal
   (3) DSA (sólo firmar)
   (4) RSA (sólo firmar)
  (14) Existing key from card
Su elección: 1
las claves RSA pueden tener entre 1024 y 4096 bits de longitud.
¿De qué tamaño quiere la clave? (3072) 4096
El tamaño requerido es de 4096 bits
Por favor, especifique el período de validez de la clave.
         0 = la clave nunca caduca
      <n>  = la clave caduca en n días
      <n>w = la clave caduca en n semanas
      <n>m = la clave caduca en n meses
      <n>y = la clave caduca en n años
¿Validez de la clave (0)? 1y
La clave caduca dom 05 mar 2023 07:56:37 CET
¿Es correcto? (s/n) s

GnuPG debe construir un ID de usuario para identificar su clave.

Nombre y apellidos: Tutorial Pass
Dirección de correo electrónico: tutorial.pass@dvdcr.com
Comentario: Usuario principal de Pass
Ha seleccionado este ID de usuario:
    "Tutorial Pass (Usuario principal de Pass) <tutorial.pass@dvdcr.com>"

¿Cambia (N)ombre, (C)omentario, (D)irección o (V)ale/(S)alir? V

En este punto un diálogo emergente nos pedirá una contraseña que tendremos que especificar dos veces.

Y luego se recomienda trabajar con el ratón o en otras ventanas para generar mayor entropía y que haya suficientes valores aleatorios para la generación de la clave.

Es necesario generar muchos bytes aleatorios. Es una buena idea realizar
alguna otra tarea (trabajar en otra ventana/consola, mover el ratón, usar
la red y los discos) durante la generación de números primos. Esto da al
generador de números aleatorios mayor oportunidad de recoger suficiente
entropía.
gpg: clave 7BA4043CB83457F9026E38CA9348B9233410DF876 marcada como de confianza absoluta
gpg: certificado de revocación guardado como '/home/usuario/.gnupg/openpgp-revocs.d/8DC10C2043C49D2A184B6083B441F579E8FD4B35.rev'
claves pública y secreta creadas y firmadas.

pub   rsa4096 2022-03-05 [SC] [caduca: 2023-03-05]
      7BA4043CB83457F9026E38CA9348B9233410DF876
uid                      Tutorial Pass (Usuario principal de Pass) <tutorial.pass@dvdcr.com>
sub   rsa4096 2022-03-05 [E] [caduca: 2023-03-05]

Podemos obtener la lista de claves gpg disponibles en el equipo con gpg --list-secret-keys.

Una vez que tenemos al menos una clave gpg podemos inicializar el almacén con el comando pass init indicando el correo electrónico asociado a la clave gpg que queremos utilizar, el comentario o el identificador hexadecimal de la clave.

pass init "Usuario principal de Pass"
mkdir: se ha creado el directorio '/home/usuario/.password-store/'
Password store initialized for Usuario principal de Pass

Ahora dispondremos del almacén de contraseñas en el directorio ~/.password-store, con lo que se puede empezar a trabajar con pass.

Opcionalmente podemos inicializar el almacén de contraseñas como un repositorio de git:

pass git init
ayuda: Usando 'master' como el nombre de la rama inicial. Este nombre de rama predeterminado
ayuda: está sujeto a cambios. Para configurar el nombre de la rama inicial para usar en todos
ayuda: de sus nuevos repositorios, que suprimirán esta advertencia, llame a:
ayuda: 
ayuda: 	git config --global init.defaultBranch <nombre>
ayuda: 
ayuda: Los nombres comúnmente elegidos en lugar de 'maestro' son 'principal', 'troncal' y
ayuda: 'desarrollo'. Se puede cambiar el nombre de la rama recién creada mediante este comando:
ayuda: 
ayuda: 	git branch -m <nombre>
Inicializado repositorio Git vacío en /home/usuario/.password-store/.git/
[master (commit-raíz) 5537e70] Add current contents of password store.
 1 file changed, 1 insertion(+)
 create mode 100644 .gpg-id
[master 4c94ae7] Configure git repository for gpg file diff.
 1 file changed, 1 insertion(+)
 create mode 100644 .gitattributes

Esto hará que cada vez que una contraseña sea manipulada se haga un git commit, lo que permitiría tener trazabilidad de los cambios y deshacerlos si es necesario.

Además, opcionalmente, podemos agregar un repositorio remoto para subir los cambios en él:

pass git remote add origin https://migit.es:pass-store

 

Operaciones frecuentes

Guardar una contraseña

Aunque haremos referencia a “contraseñas”, realmente en una entrada de pass podemos almacenar cualquier tipo de información como identificadores de usuario, direcciones IP o direcciones de correo electrónico. Incluso es posible guardar en una misma entrada múltiples líneas y utilizar alguna sintaxis clave-valor con algún separador como el signo igual o dos puntos.

Para insertar una contraseña especificaremos la jerarquía donde queremos que se guarde y pass nos preguntará el valor dos veces sin mostrar lo que tecleamos por pantalla:

pass insert bbdd/mysql/wordpress/admin
mkdir: se ha creado el directorio '/home/user/.password-store/bbdd'
mkdir: se ha creado el directorio '/home/user/.password-store/bbdd/mysql'
mkdir: se ha creado el directorio '/home/user/.password-store/bbdd/mysql/wordpress'
Enter password for bbdd/mysql/wordpress/admin: 
Retype password for bbdd/mysql/wordpress/admin: 

Para insertar una entrada que contenga múltiples líneas tendremos que utilizar el parámetro -m, teclear toda la información y pulsar CTRL+D cuando hayamos terminado:

pass insert -m wordpress/blog/config
mkdir: se ha creado el directorio '/home/user/.password-store/wordpress'
mkdir: se ha creado el directorio '/home/user/.password-store/wordpress/blog'
Enter contents of wordpress/blog/config and press Ctrl+D when finished:

define('DB_NAME', 'nombredetubasededatos');
define('DB_USER', 'nombredeusuario');
define('DB_PASSWORD', 'contraseña');
define('DB_HOST', 'localhost');
define('DB_CHARSET', 'utf8');
define('DB_COLLATE', '');

 

Mostrar contenido de una contraseña

Para mostrar el contenido podemos utilizar indistintamente el comando pass sin ningún subcomando o con los subcomandos ls o show. Si no especificamos ninguna ruta, se mostrará el árbol completo de contraseñas del almacén:

pass
Password Store
├── bbdd
│   └── mysql
│       └── wordpress
│           └── admin
└── wordpress
    └── blog
        └── config

Habríamos obtenido el mismo resultado exactamente con:

pass ls

o

pass show

Si especificamos una ruta parcial que no incluya el elemento final de la ruta, pass mostrará sólo la porción del árbol que haya bajo la ruta que hemos escrito. No nos pedirá la contraseña de cifrado de la clave gpg:

pass bbdd
bbdd
└── mysql
    └── wordpress
        └── admin
pass bbdd/mysql
bbdd/mysql
└── wordpress
    └── admin

Si especificamos la ruta completa hasta una contraseña, nos mostrará la contraseña en claro:

pass bbdd/mysql/wordpress/admin

Si es la primera vez en la sesión que solicitamos una contraseña o bien la caché de contraseñas de gpg ha caducado, nos aparecerá un diálogo solicitando la contraseña de la clave gpg:

Tras introducir la contraseña de la clave gpg, nos mostrará el valor de la contraseña cuya ruta hemos especificado. Durante el tiempo de caché que tenga definido gpg no nos volverá a solicitar la contraseña si realizamos más operaciones a continuación.

Debemos recordar también que disponemos de función de autocompletado, que nos ayuda a obtener la ruta sin teclearla completamente, ayudándonos de la tecla Tab. Si escribimos pass bbdd y pulsamos el tabulador nos autocompletará la ruta hasta el máximo posible en el que no haya ambigüedad. En este caso concreto escribirá toda la ruta bbdd/mysql/wordpress/admin hasta la clave final, puesto que sólo hay una contraseña y no hay ambigüedad.

 

Copiar contenido de una contraseña al portapapeles

Para copiar el contenido de una contraseña al portapapeles, evitando que aparezca en la terminal, utilizaremos el parámetro -c o --clip.

pass -c bbdd/mysql/wordpress/admin
Copied bbdd/mysql/wordpress/admin to clipboard. Will clear in 45 seconds.

Por defecto permanecerá en el portapapeles durante 45 segundos, durante los cuales podremos pegarla en cualquier aplicación como un navegador web o una aplicación de terminal que nos esté solicitando una contraseña. El tiempo en el que permanecerá en el portapapeles se puede modificar si definimos un valor en la variable de entorno PASSWORD_STORE_CLIP_TIME. Vamos a ver cómo subirlo a 90 segundos temporalmente:

export PASSWORD_STORE_CLIP_TIME=90
pass -c wordpress/blog/config
Copied wordpress/blog/config to clipboard. Will clear in 90 seconds.

En el caso de contraseñas en las que hayamos almacenado varias filas, sólo se copiará una de ellas, por defecto la primera.

Vamos a ver un ejemplo de ello. Mostramos el contenido de una de las claves que hemos introducido previamente:

pass wordpress/blog/config 
define('DB_NAME', 'nombredetubasededatos');
define('DB_USER', 'nombredeusuario');
define('DB_PASSWORD', 'contraseña');
define('DB_HOST', 'localhost');
define('DB_CHARSET', 'utf8');
define('DB_COLLATE', '');

Copiamos el contenido:

pass -c wordpress/blog/config
Copied wordpress/blog/config to clipboard. Will clear in 45 seconds.

Pegamos el contenido:

define('DB_NAME', 'nombredetubasededatos');

Siempre se copia una línea pero podemos precisar el número de línea en concreto especificándolo a continuación del parámetro -c o --clip:

pass -c2 wordpress/blog/config
Copied wordpress/blog/config to clipboard. Will clear in 45 seconds.

Pegamos el contenido y veremos que se ha seleccionado la línea 2:

define('DB_USER', 'nombredeusuario');

 

Generar código QR con el contenido de una contraseña

De forma muy similar a la copia al portapapeles, pero utilizando el parámetro -q o --qrcode, pass nos generará un código QR con el contenido de una contraseña:

pass -q bbdd/mysql/wordpress/admin

nos mostrará una ventana flotante con el código QR:

pass hace uso de la herramienta qrencode para ello.

Tal y como ocurría con el copiado al portapapeles, pass sólo copia una línea en el caso de entradas multilínea, por lo que si queremos una linea en concreto deberemos indicarlo poniendo el número de línea a continuación del parámetro -q o --qrcode. Si queremos que se genere el código QR con el contenido de la tercera fila:

pass -q3 wordpress/blog/config

 

Generar una contraseña aleatoria

pass puede generar nuevas contraseñas de forma aleatoria, para lo que utiliza internamente /dev/urandom.

La forma más sencilla de aprovechar esta funcionalidad es utilizar el subcomando generate sin parámetros, lo que generará un clave que incluirá todo tipo de símbolos y con una longitud de 25 caracteres:

pass generate api/geo/admin
mkdir: se ha creado el directorio '/home/user/.password-store/api/geo'
[master 8c03f82] Add generated password for api/geo/admin.
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 api/geo/admin.gpg
The generated password for api/geo/admin is:
\8Pq*F9,+.Z8VZF0MLe9%`IKt

Podemos indicar que la clave generada sólo contenga caracteres alfanuméricos si incluimos el parámetro -n o --no-symbols y limitar la longitud de la contraseña si especificamos al final del comando un número. En el siguiente ejemplo generaremos una contraseña con caracteres únicamente alfanuméricos que tendrá 12 caracteres:

pass generate -n api/geo/developer 12
[master aa44eb2] Add generated password for api/geo/developer.
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 api/geo/developer.gpg
The generated password for api/geo/developer is:
FViQ3KvuIpY2

Si no queremos que la contraseña generada aparezca en pantalla al generarse, sino que además de almacenarse en su archivo correspondiente se copie en el portapapeles, podemos añadir el parámetro --clip o -c:

pass generate -n -c api/geo/developer2 15
[master 3cec11d] Add generated password for api/geo/developer2.
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 api/geo/developer2.gpg
Copied api/geo/developer2 to clipboard. Will clear in 45 seconds.

Como variante de lo anterior, podemos indicar el parámetro -q o -qrcode para que además de almacenarse la clave en su archivo correspondiente se genere un código QR y la contraseña no salga por consola.

Si especificamos la ruta a una contraseña ya existente, nos preguntará si queremos sobreescribirla:

$ pass generate -n -c api/geo/developer2 15
An entry already exists for api/geo/developer2. Overwrite it? [y/N] y
[master f6f9663] Add generated password for api/geo/developer2.
 1 file changed, 0 insertions(+), 0 deletions(-)
 rewrite api/geo/developer2.gpg (100%)
Copied api/geo/developer2 to clipboard. Will clear in 45 seconds.

Si a la hora de actualizar una contraseña no deseamos que nos pida confirmación, podemos utilizar el parámetro -f o --force:

pass generate -n -c -f api/geo/developer2 15
[master cfb4798] Add generated password for api/geo/developer2.
 1 file changed, 0 insertions(+), 0 deletions(-)
 rewrite api/geo/developer2.gpg (89%)
Copied api/geo/developer2 to clipboard. Will clear in 45 seconds.

El parámetro -i o --in-place permite especificar que en caso de archivos multilínea sólo se reemplace el contenido de la primera línea con la contraseña generada aleatoriamente.

Vamos a generar una clave multilínea:

pass insert -m api/node/users
mkdir: se ha creado el directorio '/home/user/.password-store/api/node'
Enter contents of api/node/users and press Ctrl+D when finished:

user1:blablabla
user2:blebleble
user3:bliblibli                            
[master 392d80e] Add given password for api/node/users to store.
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 api/node/users.gpg

Generamos una contraseña con el parámetro -i:

pass generate -n -i api/node/users
[master 2837f6a] Replace generated password for api/node/users.
 1 file changed, 0 insertions(+), 0 deletions(-)
 rewrite api/node/users.gpg (100%)
The generated password for api/node/users is:
NijYp8yE19IT06A0uaG1FuG22

Comprobamos el resultado. Veremos que la primera línea ha sido reemplazada por una nueva contraseña generada al azar:

pass api/node/users
NijYp8yE19IT06A0uaG1FuG22
user2:blebleble
user3:bliblibli

Si utilizamos el subcomando generate sin -i sobre una clave multilínea existente sustituye completamente el contenido, quedando una sola línea:

pass generate -n api/node/users
An entry already exists for api/node/users. Overwrite it? [y/N] y
[master 262061f] Add generated password for api/node/users.
 1 file changed, 0 insertions(+), 0 deletions(-)
 rewrite api/node/users.gpg (100%)
The generated password for api/node/users is:
ibIF3YBTAHrcjOlr61bO2TaV3
pass api/node/users
ibIF3YBTAHrcjOlr61bO2TaV3

Podemos cambiar la longitud predeterminada de las contraseñas generadas aleatoriamente creando la variable de entorno PASSWORD_STORE_GENERATED_LENGTH con el valor de la nueva longitud que queramos establecer:

export PASSWORD_STORE_GENERATED_LENGTH=8
pass generate api/meteo/token
mkdir: se ha creado el directorio '/home/user/.password-store/api/meteo'
[master e460e6e] Add generated password for api/meteo/token.
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 api/meteo/token.gpg
The generated password for api/meteo/token is:
zU-D+WAI

Podemos personalizar el conjunto de caracteres que utilizado para generar contraseñas aleatorias. Si lo que queremos personalizar es el conjunto utilizado cuando se especifica la opción -n o --no-symbols tendremos que crear la variable de entorno PASSWORD_STORE_CHARACTER_SET_NO_SYMBOLS con una cadena que contenga los caracteres permitidos:

export PASSWORD_STORE_CHARACTER_SET_NO_SYMBOLS="ZzYyXxWxTtSsRr012"
pass generate -f -n api/meteo/token
[master 208ecd5] Add generated password for api/meteo/token.
 1 file changed, 0 insertions(+), 0 deletions(-)
 rewrite api/meteo/token.gpg (100%)
The generated password for api/meteo/token is:
txT0Yt0x

Si lo que queremos personalizar es el conjunto utilizado cuando NO se especifica la opción -n o la opción --no-symbols tendremos que crear la variable de entorno PASSWORD_STORE_CHARACTER_SET con una cadena que contenga los caracteres permitidos:

export PASSWORD_STORE_CHARACTER_SET="abcdefgABCDEFG56789_$=&$"
pass generate -f api/meteo/token
[master bf92ac1] Add generated password for api/meteo/token.
 1 file changed, 0 insertions(+), 0 deletions(-)
 rewrite api/meteo/token.gpg (100%)
The generated password for api/meteo/token is:
G$e&cdf9

 

Editar una contraseña

La modificación de una contraseña se puede realizar con los subcomandos insert o generate simplemente con el hecho de indicar la ruta de una contraseña existente. Si no queremos que se pida confirmación utilizaremos el parámetro -f o --force.

Para modificar una contraseña de forma interactiva a través de un editor existe el subcomando edit. Cuando llamamos a pass edit /ruta/a/clave se nos abre un editor de texto. Esto es especialmente util para modificar claves multilínea. El editor que se abre es el especificado en la variable de entorno EDITOR, que la mayoría de distribuciones tienen inicializada. Si no tiene valor, se utilizará vi por defecto. La edición se realiza sobre un archivo creado en un directorio temporal, habitualmente /dev/shm. Este directorio en realidad no se encuentra en disco sino que es un montaje realizado en memoria virtual y que por lo tanto reduce el riesgo de recuperación de contraseñas almacenadas en disco físico. Si este sistema de archivos no estuviera disponible, se utilizaría el directorio temporal estándar y se emitiría un aviso.

 

Eliminar una contraseña

La eliminación de una contraseña puede hacerse con los subcomandos rm, remove o delete. Se puede especificar el parámetro -r o --recursive para eliminar toda una rama del árbol de contraseñas. Si queremos evitar que nos pida confirmación podemos utilizar los parámetros -f o --force:

 pass api/meteo
api/meteo
└── token
pass rm -r -f api/meteo
'/home/user/.password-store/api/meteo/token.gpg' borrado
removed directory '/home/user/.password-store/api/meteo/'
[master 4b0b3fa] Remove api/meteo from store.
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 api/meteo/token.gpg

 

Copiar una contraseña a otra ruta del almacén

Podemos copiar una contraseña o directorio completo a una nueva ruta con los subcomandos copy o cp. Pedirá confirmación para sobreescribir el destino si ya existe. Se puede omitir la confirmación si se especifica el parámetro -f o --force.

Si la ruta destino termina en / se considerará un directorio.

Si la ruta destino está cifrada con una clave pgp diferente de la del origen, las contraseñas serán reencriptadas con la clave que le corresponde según su nueva ruta.

pass wordpress
wordpress
└── blog
    └── config
pass cp wordpress/blog wordpress/ecommerce
/home/user/.password-store/wordpress/blog
'/home/user/.password-store/wordpress/blog' -> '/home/user/.password-store/wordpress/ecommerce'
'/home/user/.password-store/wordpress/blog/config.gpg' -> '/home/user/.password-store/wordpress/ecommerce/config.gpg'
[master 4999bd9] Copy wordpress/blog to wordpress/ecommerce.
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 wordpress/ecommerce/config.gpg
pass wordpress
wordpress
├── blog
│   └── config
└── ecommerce
    └── config

 

Mover una contraseña a otra ruta del almacén

Con los subcomandos mv o rename podemos cambiar el nombre o directorio donde se encuentra una contraseña. Podemos especificar los parámetros -f o --force para evitar que nos pida confirmación antes de realizar la operación. Si la nueva ruta termina en / se considerará un directorio. Si se mueve una contraseña a un directorio que está encriptado con una clave diferente de la del directorio origen, las contraseñas serán reencriptadas para que se correspondan con la clave de su nuevo destino.

pass wordpress
wordpress
├── blog
│   └── config
└── ecommerce
    └── config
pass mv -f wordpress/ecommerce wordpress/tienda
/home/user/.password-store/wordpress/ecommerce
renamed '/home/user/.password-store/wordpress/ecommerce' -> '/home/user/.password-store/wordpress/tienda'
[master aebbaca] Rename wordpress/ecommerce to wordpress/tienda.
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename wordpress/{ecommerce => tienda}/config.gpg (100%)
pass wordpress
wordpress
├── blog
│   └── config
└── tienda
    └── config

 

Buscar contraseñas en el almacén

Los subcomandos find y search buscan coincidencias parciales en el nombre de una contraseña o en su ruta. Mostrará el árbol completo en el que se encuentren elementos que coincidan con la cadena de búsqueda.

pass find wordpress
Search Terms: wordpress
├── bbdd
│   └── mysql
│       └── wordpress
│           └── admin
└── wordpress
    ├── blog
    │   └── config
    └── tienda
        └── config

El subcomando grep realiza la búsqueda por coincidencia parcial en el contenido de una contraseña. Mostrará la ruta hasta la contraseña o contraseñas encontradas y su contenido completo. La sintaxis es:

pass grep <opciones> <cadena-de-búsqueda>

<opciones> serán parámetros admitidos por el comando grep y se pasarán tal cual a dicho comando para hacer la búsqueda. El siguiente comando realiza una búsqueda en el contenido ignorando mayúsculas y minúsculas:

pass grep -i db_password
wordpress/blog/config:
define('DB_PASSWORD', 'contraseña');
wordpress/tienda/config:
define('DB_PASSWORD', 'contraseña');

En el siguiente comando no incluimos el parámetro -i:

pass grep db_password

No devuelve nada porque no encuentra ningún resultado ya que el contenido está en mayúsculas y no hemos incluído el parámetro -i.

Si buscamos sin -ipero especificando la cadena de búsqueda en mayúsculas sí que encuentra resultados:

pass grep DB_PASSWORD
wordpress/blog/config:
define('DB_PASSWORD', 'contraseña');
wordpress/tienda/config:
define('DB_PASSWORD', 'contraseña');

 

Utilización de un almacén por múltiples usuarios

Es posible la utilización de un almacén por múltiples usuarios, cada uno con su propia clave gpg. Podemos tener directorios de claves sobre los que tengan permisos varios usuarios o solamente alguno de ellos.

Vamos a partir de un equipo en el que tenemos tres claves:

gpg --list-keys
gpg: comprobando base de datos de confianza
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: nivel: 0  validez:   3  firmada:   0  confianza: 0-, 0q, 0n, 0m, 0f, 3u
gpg: siguiente comprobación de base de datos de confianza el: 2023-03-05
/home/user/.gnupg/pubring.kbx
-----------------------------
pub   rsa4096 2022-03-05 [SC] [caduca: 2023-03-05]
      8DC10C2043C49D2A184B6083B441F579E8FD4B35
uid        [  absoluta ] Tutorial Pass (Usuario principal de Pass) <tutorial.pass@dvdcr.com>
sub   rsa4096 2022-03-05 [E] [caduca: 2023-03-05]

pub   rsa3072 2022-03-12 [SC]
      481F470ACD42A060A43823EADF8598AD17670555
uid        [  absoluta ] Departamento contabilidad (contabilidad) <contabilidad@dvdcr.com>
sub   rsa3072 2022-03-12 [E]

pub   rsa3072 2022-03-12 [SC]
      8B84A215584C363F0DF905622E3766B165193EFD
uid        [  absoluta ] Departamento Auditoría (auditoria) <auditoria@dvdcr.com>
sub   rsa3072 2022-03-12 [E]

Vamos a inicializar un directorio dentro de nuestro almacén al que sólo tendrá acceso el departamento de contabilidad con su clave:

pass init -p contabilidad/privado contabilidad
mkdir: se ha creado el directorio '/home/user/.password-store/contabilidad'
mkdir: se ha creado el directorio '/home/user/.password-store/contabilidad/privado'
Password store initialized for contabilidad (contabilidad/privado)
[master 49e7c31] Set GPG id to contabilidad (contabilidad/privado).
 1 file changed, 1 insertion(+)
 create mode 100644 contabilidad/privado/.gpg-id

Generamos una clave para el correo electrónico de contabilidad:

pass generate -n contabilidad/privado/email
pass init -p contabilidad/privado contabilidad
mkdir: se ha creado el directorio '/home/user/.password-store/contabilidad'
mkdir: se ha creado el directorio '/home/user/.password-store/contabilidad/privado'
Password store initialized for contabilidad (contabilidad/privado)
[master 49e7c31] Set GPG id to contabilidad (contabilidad/privado).
 1 file changed, 1 insertion(+)
 create mode 100644 contabilidad/privado/.gpg-id
[dvdc@baracus ~]$ pass generate -n contabilidad/privado/email
[master 9162f15] Add generated password for contabilidad/privado/email.
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 contabilidad/privado/email.gpg
The generated password for contabilidad/privado/email is:
JJGtJAWZ2yeD5cbSS30WO2l9s

Intentamos copiar al portapapeles esa nueva clave:

pass -c contabilidad/privado/email

Nos pedirá la contraseña de la clave con la que hemos cifrado la rama a la que pertenece esa entrada:

Copied contabilidad/privado/email to clipboard. Will clear in 45 seconds.

Vamos a inicializar un directorio dentro de nuestro almacén al que tendrán acceso tanto el departamento de contabilidad como el de auditoría con sus respectivas claves:

pass init -p contabilidad/publico contabilidad auditoria
mkdir: se ha creado el directorio '/home/user/.password-store/contabilidad/publico'
Password store initialized for contabilidad, auditoria (contabilidad/publico)
[master 48f6c9e] Set GPG id to contabilidad, auditoria (contabilidad/publico).
 1 file changed, 2 insertions(+)
 create mode 100644 contabilidad/publico/.gpg-id

Generamos una nueva clave para la aplicación de facturación dentro de ese directorio:

pass generate -n contabilidad/publico/facturacion
[master 518614c] Add generated password for contabilidad/publico/facturacion.
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 contabilidad/publico/facturacion.gpg
The generated password for contabilidad/publico/facturacion is:
jvAY6kjsvpSuzfgJiXHQvj12F
pass contabilidad
contabilidad
├── privado
│   └── email
└── publico
    └── facturacion

Intentamos copiarla al portapapeles.

pass -c contabilidad/publico/facturacion

En nuestro caso, como en el mismo equipo constan tanto las claves de contabilidad como las de auditoría, nos pedirá en primer lugar la contraseña de una de ellas:

y si cancelamos nos preguntará la clave del departamento de auditoría:

Copied contabilidad/publico/facturacion to clipboard. Will clear in 45 seconds.

Cualquiera de las dos claves privadas habría servido para consultar la contraseña de facturación.

Ahora veamos qué ocurre cuando copiamos una clave de un directorio asociado a una clave privada, como ocurre con wordpress/tienda/config, que estaba asociada al usuario principal y por lo tanto no era accesible por las claves de auditoría y contabilidad, a otro con una clave distinta, como es contabilidad/publico:

pass find contabilidad wordpress/tienda
Search Terms: contabilidad,wordpress/tienda
├── contabilidad
│   ├── privado
│   │   └── email
|   └── publico
│       └── facturacion
└── wordpress
    └── tienda
        └── config
pass mv wordpress/tienda contabilidad/publico
pass mv -f wordpress/tienda contabilidad/publico/
/home/user/.password-store/wordpress/tienda
renamed '/home/user/.password-store/wordpress/tienda' -> '/home/user/.password-store/contabilidad/publico/tienda'
contabilidad/publico/tienda/config: reencrypting to 71AC1CC8EF0578B4 B9E9C8615602D819
[master 206ff99] Rename wordpress/tienda to contabilidad/publico/.
 2 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 contabilidad/publico/tienda/config.gpg
 delete mode 100644 wordpress/tienda/config.gpg

Vemos en el mensaje que la clave ha sido reencriptada (reencrypting to 71AC1CC8EF0578B4 B9E9C8615602D819).

La estructura de claves ha quedado así:

pass find contabilidad wordpress/
Search Terms: contabilidad,wordpress/
├── contabilidad
│   ├── privado
│   │   └── email
│   └── publico
│       ├── facturacion
│       └── tienda
│           └── config
└── wordpress
    └── blog
        └── config

Tratamos de acceder a la clave de la tienda:

pass -c contabilidad/publico/tienda/config
Copied contabilidad/publico/tienda/config to clipboard. Will clear in 45 seconds.

Vemos que hay acceso a ella simplemente porque tenemos cacheada una de las dos claves. Si hubiera pasado un tiempo y ya no estuviera en caché, nos habría pedido la clave de auditoría o contabilidad.

 

Utilización de pass en comandos y scripts

Puede que queramos que ciertas contraseñas no queden en el historial de comandos ejecutados. También puede que esos comandos formen parte de un script que va a estar en un repositorio Git público y no queremos que esté expuesto el usuario ni la contraseña.

Un ejemplo de ello puede ser una llamada a una API que requiere usuario y contraseña:

curl -u admin:clave-secret -X GET http://localhost:9200/_cluster/health

Una posible solución a este caso es utilizar variables de entorno o bien utilizar directamente una llamada al comando pass.

Primero almacenamos el usuario y la clave:

pass insert api/elastic/user
mkdir: se ha creado el directorio '/home/user/.password-store/api'
mkdir: se ha creado el directorio '/home/user/.password-store/api/elastic'
Enter password for api/elastic/user: 
Retype password for api/elastic/user: 
[master 413ac44] Add given password for api/elastic/user to store.
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 api/elastic/user.gpg
pass insert api/elastic/password
Enter password for api/elastic/password: 
Retype password for api/elastic/password: 
[master becd064] Add given password for api/elastic/password to store.
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 api/elastic/password.gpg

Ahora podemos utilizar el comando pass correspondiente encerrado entre $() para sustituir el usuario y contraseña:

curl -u $(pass api/elastic/user):$(pass api/elastic/password) -X GET http://localhost:9200/_cluster/health

 

Uso de git

Como se ha especificado en el apartado de inicialización de una almacén de claves, es posible inicializar un almacén de claves como repositorio git:

pass git init

Esto hace que cualquier operación sobre el almacén quede registrada en un commit:

pass generate -f -n api/geo/developer2
pass git log
commit 56a1ff865436d81bfb5e9b9cb8c280c8339b0132 (HEAD -> master)
Author: dvdcr <dvdcr@dvdcr.com>
Date:   Sat Mar 12 06:41:19 2022 +0100

    Add generated password for api/geo/developer2.

commit 682642ef6a5e932e1018a1290df4dbdd31b2115b
Author: dvdcr <dvdcr@dvdcr.com>
Date:   Sat Mar 12 06:40:10 2022 +0100

    Add current contents of password store.

Podemos vincular un repositorio remoto:

pass git remote add origin https://gitlab.com/dvdcr/pass-store

Realizamos una operación adicional:

pass generate -n -f api/geo/developer3
1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 api/geo/developer3.gpg
The generated password for api/geo/developer3 is:
5jzrsabiM7YanVZp4iIu9pLJO

Vemos que aparece la última operación en el historial local:

pass git log
commit 3b2c2dbc090787b4b01379ad3fec680617cb59b7 (HEAD -> master)
Author: dvdcr <dvdcr@dvdcr.com>
Date:   Sat Mar 12 06:45:58 2022 +0100

    Add generated password for api/geo/developer3.

commit 56a1ff865436d81bfb5e9b9cb8c280c8339b0132
Author: dvdcr <dvdcr@dvdcr.com>
Date:   Sat Mar 12 06:41:19 2022 +0100

    Add generated password for api/geo/developer2.

commit 682642ef6a5e932e1018a1290df4dbdd31b2115b
Author: dvdcr <dvdcr@dvdcr.com>
Date:   Sat Mar 12 06:40:10 2022 +0100

    Add current contents of password store.

Pero además podemos subir los cambios al repositorio remoto y tener un backup de una forma rápida y sencilla (siempre que confiemos en el repositorio remoto). Podremos utilizar cualquier comando de git, en este caso un push. Como acabamos de vincular el repositorio y no existe la rama en el repositorio remoto, tendremos que utilizar el parámetro -u, pero las siguientes veces no será necesario:

pass git push -u --all
Enumerando objetos: 40, listo.
Contando objetos: 100% (40/40), listo.
Compresión delta usando hasta 4 hilos
Comprimiendo objetos: 100% (29/29), listo.
Escribiendo objetos: 100% (40/40), 10.54 KiB | 1.32 MiB/s, listo.
Total 40 (delta 4), reusados 0 (delta 0), pack-reusados 0
To https://gitlab.com/dvdcr/pass-store
 * [new branch]      master -> master
Rama 'master' configurada para hacer seguimiento a la rama remota 'master' de 'origin'.

Podemos comprobar a través de la web del repositorio que se ha subido el contenido del directorio que contiene las claves:

 

Exportar la base de datos de pass a otro equipo

El proceso de exportación del almacén de claves para su utilización en otro equipo es sencillo.

Por un lado está el contenido del almacén. Basta con copiarnos todo el contenido de ~/.password-store (o la ruta personalizada si no utilizamos la ruta por defecto) al otro equipo en esa misma ruta. La forma de copiarla es indiferente. Podemos copiar directamente con scp si tenemos acceso por ssh a la máquina destino o bien utilizar un pendrive o recurso compartido intermedio.

Ejemplo de copia del almacén con scp:

scp -r ~/.password-store/ usuario@192.168.1.10:/home/user/

Si en ordenador destino intentamos obtener una de las contraseñas nos informará de que no tenemos una clave de descifrado apta para este almacén:

pass -c api/elastic/password
gpg: descifrado fallido: No tenemos la clave secreta 

Para poder acceder al almacén debemos disponer de la clave o claves gpg en el equipo destino. Para ello comprobamos en el equipo origen la lista de claves:

gpg --list-keys
home/dvdc/.gnupg/pubring.kbx
-----------------------------
pub   rsa4096 2022-03-05 [SC] [caduca: 2023-03-05]
      8DC10C2043C49D2A184B6083B441F579E8FD4B35
uid        [  absoluta ] Tutorial Pass (Usuario principal de Pass) <tutorial.pass@dvdcr.com>
sub   rsa4096 2022-03-05 [E] [caduca: 2023-03-05]

pub   rsa3072 2022-03-12 [SC]
      481F470ACD42A060A43823EADF8598AD17670555
uid        [  absoluta ] Departamento contabilidad (contabilidad) <contabilidad@dvdcr.com>
sub   rsa3072 2022-03-12 [E]

pub   rsa3072 2022-03-12 [SC]
      8B84A215584C363F0DF905622E3766B165193EFD
uid        [  absoluta ] Departamento Auditoría (auditoria) <auditoria@dvdcr.com>
sub   rsa3072 2022-03-12 [E]

Hacemos la exportación de la clave privada y pública de cada usuario, utilizando como identificador el correo o hash. Nos pedirá la contraseña de cada una de las claves privadas:

ID_KEY=tutorial.pass@dvdcr.com
gpg --output tutorial_public.gpg --armor --export $ID_KEY
gpg --output tutorial_private.gpg --armor --export-secret-key $ID_KEY
ID_KEY=contabilidad@dvdcr.com
gpg --output contabilidad_public.gpg --armor --export $ID_KEY
gpg --output contabilidad_private.gpg --armor --export-secret-key $ID_KEY
ID_KEY=auditoria@dvdcr.com
gpg --output auditoria_public.gpg --armor --export $ID_KEY
gpg --output auditoria_private.gpg --armor --export-secret-key $ID_KEY

El resultado es el siguiente:

ls -l
total 36
-rw-------. 1 dvdc 5229 mar 12 07:08 auditoria_private.gpg
-rw-rw-r--. 1 dvdc 2472 mar 12 07:08 auditoria_public.gpg
-rw-------. 1 dvdc 5237 mar 12 07:08 contabilidad_private.gpg
-rw-rw-r--. 1 dvdc 2480 mar 12 07:08 contabilidad_public.gpg
-rw-------. 1 dvdc 6817 mar 12 07:08 tutorial_private.gpg
-rw-rw-r--. 1 dvdc 3195 mar 12 07:08 tutorial_public.gpg

Copiamos al ordenador destino las claves por cualquiera de los métodos conocidos. Ejemplo de copia con scp:

ssh usuario@192.168.1.10 mkdir /home/user/gpg-keys
scp *.gpg usuario@192.168.1.10:/home/user/gpg-keys/

y las importamos en el ordenador destino con el comando gpg --import:

gpg --import *_public.gpg
gpg: /home/alumno/.gnupg/trustdb.gpg: se ha creado base de datos de confianza
gpg: clave 2E3766B165193EFD: clave pública "Departamento Auditoría (auditoria) <auditoria@dvdcr.com>" importada
gpg: clave DF8598AD17670555: clave pública "Departamento contabilidad (contabilidad) <contabilidad@dvdcr.com>" importada
gpg: clave B441F579E8FD4B35: clave pública "Tutorial Pass (Usuario principal de Pass) <tutorial.pass@dvdcr.com>" importada
gpg: Cantidad total procesada: 3
gpg:               importadas: 3
gpg --allow-secret-key-import --import *_private.gpg

Nos pedirá la contraseña para cada una de las claves privadas en un diálogo que será en modo gráfico o texto dependiendo del equipo destino. Aquí un ejemplo de un diálogo en modo texto:

gpg --allow-secret-key-import --import *_private.gpg
gpg: clave 2E3766B165193EFD: "Departamento Auditoría (auditoria) <auditoria@dvdcr.com>" sin cambios
gpg: clave 2E3766B165193EFD: clave secreta importada
gpg: clave DF8598AD17670555: "Departamento contabilidad (contabilidad) <contabilidad@dvdcr.com>" sin cambios
gpg: clave DF8598AD17670555: clave secreta importada
gpg: clave B441F579E8FD4B35: "Tutorial Pass (Usuario principal de Pass) <tutorial.pass@dvdcr.com>" sin cambios
gpg: clave B441F579E8FD4B35: clave secreta importada
gpg: Cantidad total procesada: 3
gpg:              sin cambios: 3
gpg:       claves secretas leídas: 3
gpg:   claves secretas importadas: 3
gpg:  claves secretas sin cambios: 1

Después de la importación de las claves será posible acceder a las contraseñas.

 

Extensiones

pass admite extensiones para soportar una mayor variedad de casos de uso. Las extensiones que se instalan en la ruta /usr/lib/password-store/extensions están siempre habilitadas. Las extensiones instaladas en ~/.password-store/.extensions/ estáh habilitadas si la variable de entorno PASSWORD_STORE_ENABLE_EXTENSIONS tiene valor true.

Las extensiones son realizadas por la comunidad. Puede consultarse una lista de ellas en https://www.passwordstore.org/#extensions.

Vamos a probar una extensión para importar claves desde múltiples gestores de contraseñas, concretamente la extensión pass-import, que podemos encontrar en https://github.com/roddhjav/pass-import/.

git clone https://github.com/roddhjav/pass-import/
cd pass-import
sudo python3 setup.py install
sudo pip3 install pykeepass

Una vez instalada la exensión podemos instalar por ejemplo las contraseñas almacenadas en un almacén de Keepass. Dependiendo del tipo de almacén origen puede que requiera instalar algún componente más, pero si es necesario nos informará de ello al intentar utilizarla.

El uso general de la herramienta utiliza la sintaxis:

pass import ruta/al/almacen

Si utilizamos esa sintaxis, la extensión intentará detectar el formato del almacén del que queremos importar las contraseñas. Si no lo detecta o hay ambigüedad y queremos especificarlo, podemos utilizar la sintaxis:

pass import gestor_contraseñas ruta/al/almacen

Ejemplo de importación de un almacén de Keepassxc:

pass import keepassxc almacen.kdbx
Password for almacen.kdbx: 
 (*) Importing passwords from keepassxc to pass
  .  Passwords imported from: almacen.kdbx
  .  Passwords exported to: /home/user/.password-store
  .  Number of password imported: 4
  .  Passwords imported:
	compras/aliexpress
	compras/amazon
	correo/gmail
	correo/outlook

Al instalar la expensión se incluye también el comando pimport que permite exportar un almacén de un tipo determinado a otro que no sea necesariamente pass. La sintaxis en este caso es:

pimport gestor_contraseñas_destino gestor_contraseñas_origen ruta/a/contraseñas/origen \
--out ruta/a/contraseñas/destino

 

Aplicaciones cliente compatibles

Además del comando pass existen varias aplicaciones cliente compatibles con los almacenes de claves de pass que han sido creadas por otros programadores para diferentes sistemas y/o herramientas. Existen plugins de navegador para Chrome y Firefox, aplicaciones móviles para Android e iOS, paquetes para emacs, aplicaciones de escritorio para Windows, Linux y Mac, etc.

Podemos encontrar una lista de aplicaciones en https://www.passwordstore.org/#other

 

Importar claves desde otras herramientas

Existen herramientas creadas por usuarios de pass que permiten importar datos desde otros gestores de contraseñas como One Password, Keepass, Lastpass, Kwallet o Firefox, entre otros.

La lista completa de herramientas y el enlace a su descarga está en está en https://www.passwordstore.org#migration.

Como ejemplo de importación de datos procedentes de Keepass a partir de un archivo de exportación CSV podemos descargar el script de importación correspondiente:

curl https://git.zx2c4.com/password-store/plain/contrib/importers/keepass2csv2pass.py \
-o ~/bin/keepass2csv2pass.py

chmod +x ~/bin/keepass2csv2pass.py

Exportamos un almacén de Keepass a formato CSV con el menú Base de datos > Exportar > Archivo CSV...

y lo importamos con el script:

./keepass2csv2pass.py archivo.csv

 

Variables de entorno

Mediante la definición de variables de entorno, se pueden configurar ciertos aspectos del comportamiento de pass.

Variable Utilidad
PASSWORD_STORE_DIR Permite especificar un directorio de almacenamiento de claves diferente del predeterminado (~/.password-store)
PASSWORD_STORE_KEY Sobreescribe la clave de gpg establecida por defecto para la identificación al ejecutar el comando pass init. Los nombres de las claves no deben contener espacios y por ello se recomienda utilizar la firma hexadecimal. Se pueden especificar múltiples claves separadas por espacios.
PASSWORD_STORE_GPG_OPTS Opciones adicionales que se pasarán a todas las llamadas que se hacen a gpg.
PASSWORD_STORE_X_SELECTION Sobreescribe la selección que se pasa al comando xclip, por defecto el portapapeles.
PASSWORD_STORE_CLIP_TIME Especifica el número de segundos que permanece una clave copiada en el portapepeles antes de restaurar su contenido previo. Por defecto 45 segundos.
PASSWORD_STORE_UMASK Establece el enmascaramiento (umask) de todos los archivos que sean modificados por pass, por defecto 077.
PASSWORD_STORE_GENERATED_LENGTH Longitud predeterminada de las contraseñas creadas con el comando pass generate si no se especifica una longitud..
PASSWORD_STORE_CHARACTER_SET Conjunto de caracteres utilzados por el comando generate para crear una nueva contraseña cuando no se utiliza el parámetro -n o --no-symbols. El valor será interpretado por tr.
PASSWORD_STORE_CHARACTER_SET_NO_SYMBOLS Conjunto de caracteres utilizados por el comando generate para crear una nueva contraseña cuando se utiliza el parámetro -n o --no-symbols. El valor será interpretado por tr.
PASSWORD_STORE_ENABLE_EXTENSIONS Esta variable de entorno debe tener el valor true para que se habiliten las extensiones.
PASSWORD_STORE_EXTENSIONS_DIR Ubicación donde se buscarán los archivos ejecutables de las extensiones, por defecto ${PASSWORD_STORE_DIR}/.extensions.
PASSWORD_STORE_SIGNING_KEY Si se establece esta variable de entorno, todos los archivos .gpg-idy todos los archivos de extensiones deben estar firmados utilizando una firma separada que utilice la clave GPG especificada por la huella completa de 40 caracteres en mayúsculas que contenga esta variable. Si se especifican varias huellas, cada una de ellas separada por espacios en blanco, las firmas deben coincidir con al menos una de ellas. El comando ìnit mantendrá las firmas de los archivos .gpg-id actualizadas.
EDITOR Ubicación del ejecutable del editor de texto que aparece cuando ejecutamos pass edit.

 

Referencias