Scripts Prácticos para Keychain
security
rsa
encryption
macos
keychain
Tutorial práctico para almacenar y recuperar claves privadas RSA de forma segura usando macOS Keychain
Objetivo
En esta lección aprenderás:
- A usar scripts listos para producción
- Manejo de errores robusto
- Automatización del flujo de trabajo con Keychain
Script: store-key-keychain.sh
#!/bin/bash
# store-key-keychain.sh
# Almacena una clave RSA en macOS Keychain con codificación Base64
set -euo pipefail
# Colores para output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Funciones de ayuda
error() {
echo -e "${RED}❌ ERROR: $1${NC}" >&2
exit 1
}
success() {
echo -e "${GREEN}✅ $1${NC}"
}
info() {
echo -e "${YELLOW}ℹ️ $1${NC}"
}
# Validar argumentos
if [ $# -lt 1 ]; then
cat <<EOF
Uso: $0 <clave-archivo> [nombre-servicio]
Argumentos:
clave-archivo Ruta al archivo de clave privada (PEM)
nombre-servicio Nombre del servicio en Keychain (opcional)
Ejemplo:
$0 ~/.ssh/id_rsa my-ssh-key
$0 /path/to/server.key production-api-key
EOF
exit 1
fi
KEY_FILE="$1"
SERVICE_NAME="${2:-$(basename "$KEY_FILE")}"
ACCOUNT="${USER:-$(whoami)}"
# Validar que el archivo existe
if [ ! -f "$KEY_FILE" ]; then
error "El archivo '$KEY_FILE' no existe"
fi
# Validar que es una clave PEM válida
if ! openssl rsa -in "$KEY_FILE" -check -noout 2>/dev/null; then
error "El archivo '$KEY_FILE' no es una clave RSA válida"
fi
# Verificar si ya existe una entrada con el mismo nombre
if security find-generic-password -a "$ACCOUNT" -s "$SERVICE_NAME" &>/dev/null; then
info "Ya existe una entrada con el nombre '$SERVICE_NAME'"
read -p "¿Deseas actualizarla? (s/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Ss]$ ]]; then
security delete-generic-password -a "$ACCOUNT" -s "$SERVICE_NAME"
info "Entrada anterior eliminada"
else
error "Operación cancelada"
fi
fi
# Codificar en Base64 y almacenar
ENCODED_KEY=$(cat "$KEY_FILE" | base64)
security add-generic-password \
-a "$ACCOUNT" \
-s "$SERVICE_NAME" \
-w "$ENCODED_KEY"
# Verificar que se almacenó correctamente
if security find-generic-password -a "$ACCOUNT" -s "$SERVICE_NAME" -w &>/dev/null; then
success "Clave almacenada exitosamente"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📋 Detalles:"
echo " Cuenta: $ACCOUNT"
echo " Servicio: $SERVICE_NAME"
echo " Archivo: $KEY_FILE"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Para recuperar la clave, usa:"
echo " retrieve-key-keychain.sh $SERVICE_NAME"
else
error "No se pudo verificar el almacenamiento"
fiScript: retrieve-key-keychain.sh
#!/bin/bash
# retrieve-key-keychain.sh
# Recupera una clave RSA de macOS Keychain y la decodifica de Base64
set -euo pipefail
# Colores para output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Funciones de ayuda
error() {
echo -e "${RED}❌ ERROR: $1${NC}" >&2
exit 1
}
success() {
echo -e "${GREEN}✅ $1${NC}"
}
info() {
echo -e "${YELLOW}ℹ️ $1${NC}"
}
# Validar argumentos
if [ $# -lt 1 ]; then
cat <<EOF
Uso: $0 <nombre-servicio> [archivo-salida]
Argumentos:
nombre-servicio Nombre del servicio en Keychain
archivo-salida Archivo donde guardar la clave (opcional)
Si no se especifica, muestra en stdout
Ejemplos:
$0 my-ssh-key # Muestra la clave en pantalla
$0 my-ssh-key ~/.ssh/restored_key # Guarda en archivo
$0 production-api-key /tmp/api.pem
EOF
exit 1
fi
SERVICE_NAME="$1"
OUTPUT_FILE="${2:-}"
ACCOUNT="${USER:-$(whoami)}"
# Verificar que la entrada existe
if ! security find-generic-password -a "$ACCOUNT" -s "$SERVICE_NAME" &>/dev/null; then
error "No se encontró ninguna entrada con el nombre '$SERVICE_NAME'"
fi
# Recuperar la clave
ENCODED_KEY=$(security find-generic-password -a "$ACCOUNT" -s "$SERVICE_NAME" -w 2>/dev/null)
if [ -z "$ENCODED_KEY" ]; then
error "No se pudo recuperar la clave"
fi
# Decodificar de Base64
DECODED_KEY=$(echo "$ENCODED_KEY" | base64 -d)
# Verificar que es una clave válida
if ! echo "$DECODED_KEY" | openssl rsa -check -noout 2>/dev/null; then
error "La clave recuperada no es válida o está corrupta"
fi
# Output según el caso
if [ -n "$OUTPUT_FILE" ]; then
# Guardar en archivo
echo "$DECODED_KEY" > "$OUTPUT_FILE"
# Establecer permisos restrictivos para claves privadas
chmod 600 "$OUTPUT_FILE"
success "Clave recuperada y guardada en '$OUTPUT_FILE'"
# Verificación de integridad final
if openssl rsa -in "$OUTPUT_FILE" -check -noout 2>/dev/null; then
success "Verificación de integridad exitosa"
else
error "La clave guardada no pasa la verificación de integridad"
fi
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📋 Detalles:"
echo " Servicio: $SERVICE_NAME"
echo " Archivo: $OUTPUT_FILE"
echo " Permisos: $(stat -f '%Lp' "$OUTPUT_FILE")"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
else
# Mostrar en stdout
echo "$DECODED_KEY"
fiUso de los Scripts
Instalación
# Crear directorio de scripts si no existe
mkdir -p ~/bin
# Copiar scripts
cp store-key-keychain.sh ~/bin/
cp retrieve-key-keychain.sh ~/bin/
# Hacer ejecutables
chmod +x ~/bin/store-key-keychain.sh
chmod +x ~/bin/retrieve-key-keychain.sh
# Agregar a PATH (si no está)
echo 'export PATH="$HOME/bin:$PATH"' >> ~/.zshrc
source ~/.zshrcEjemplos de uso
# Almacenar clave SSH personal
store-key-keychain.sh ~/.ssh/id_rsa_personal github-personal
# Almacenar clave de servidor
store-key-keychain.sh /path/to/production.key production-server
# Recuperar y mostrar en pantalla
retrieve-key-keychain.sh github-personal
# Recuperar y guardar en archivo
retrieve-key-keychain.sh production-server /tmp/production.key
# Usar directamente con SSH
retrieve-key-keychain.sh github-personal | ssh-add -Verificación Automática
Script adicional para verificar que todo funciona:
#!/bin/bash
# verify-keychain-storage.sh
set -euo pipefail
echo "🧪 Iniciando verificación de almacenamiento en Keychain..."
echo ""
# Crear clave de prueba
TEST_KEY=$(mktemp)
TEST_SERVICE="keychain-test-$(date +%s)"
echo "1️⃣ Generando clave de prueba..."
openssl genrsa -out "$TEST_KEY" 2048 2>/dev/null
echo " ✓ Clave generada: $TEST_KEY"
echo ""
echo "2️⃣ Almacenando en Keychain..."
store-key-keychain.sh "$TEST_KEY" "$TEST_SERVICE"
echo " ✓ Almacenamiento completado"
echo ""
echo "3️⃣ Recuperando de Keychain..."
RECOVERED_KEY=$(mktemp)
retrieve-key-keychain.sh "$TEST_SERVICE" "$RECOVERED_KEY"
echo " ✓ Recuperación completada"
echo ""
echo "4️⃣ Verificando integridad..."
ORIGINAL_HASH=$(shasum -a 256 "$TEST_KEY" | cut -d' ' -f1)
RECOVERED_HASH=$(shasum -a 256 "$RECOVERED_KEY" | cut -d' ' -f1)
if [ "$ORIGINAL_HASH" = "$RECOVERED_HASH" ]; then
echo " ✓ Los hashes coinciden"
else
echo " ✗ ERROR: Los hashes no coinciden"
exit 1
fi
echo ""
echo "5️⃣ Verificando validez criptográfica..."
if openssl rsa -in "$RECOVERED_KEY" -check -noout 2>/dev/null; then
echo " ✓ Clave criptográficamente válida"
else
echo " ✗ ERROR: Clave inválida"
exit 1
fi
echo ""
echo "6️⃣ Limpiando..."
security delete-generic-password -a "$USER" -s "$TEST_SERVICE" 2>/dev/null
rm -f "$TEST_KEY" "$RECOVERED_KEY"
echo " ✓ Limpieza completada"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "✅ Verificación exitosa: El sistema funciona correctamente"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"Mejores Prácticas
Seguridad
- Permisos restrictivos: Siempre usa
chmod 600para archivos de claves - Nombres descriptivos: Usa nombres de servicio que identifiquen el propósito
- Rotación periódica: Cambia las claves almacenadas regularmente
Organización
- Convención de nombres:
ssh-personal- Clave SSH personalssh-work-github- Clave SSH trabajo GitHubapi-production- Clave API produccióntls-domain-com- Certificado TLS
- Documentación: Mantén un registro de qué servicios usas
Backup
# Listar todas las entradas de claves (sin mostrar valores)
security dump-keychain | grep -A 5 "svce.*key"¿Qué Aprendimos?
- Scripts robustos: Manejo de errores y validaciones
- UX amigable: Mensajes claros y colores para identificar estados
- Verificación automática: Siempre validar antes y después
- Prácticas seguras: Permisos correctos y nombres organizados
En el siguiente laboratorio pondremos en práctica todo lo aprendido.