Skill: ivangdavila/encryption (version 1.0.0)

--- name: Encryption description: Encrypt files, secure passwords, manage keys, and audit code for cryptographic best practices. --- ## When to Use - Encrypting files, database fields, or app storage - Password hashing (bcrypt, argon2) - Key management, rotation, derivation - TLS/certificate configuration - Auditing code for crypto mistakes - Mobile secure storage (Keychain, Keystore) ## Algorithm Selection | Purpose | Use | Avoid | |---------|-----|-------| | Passwords | argon2id, bcrypt (cost≥12) | MD5, SHA1, plain SHA256 | | Symmetric | AES-256-GCM, ChaCha20-Poly1305 | AES-ECB, DES, RC4 | | Asymmetric | RSA-4096+OAEP, Ed25519, P-256 | RSA-1024, PKCS#1 v1.5 | | Key derivation | PBKDF2 (≥600k), scrypt, argon2 | Single-pass hash | | JWT signing | RS256, ES256 | HS256 with weak secret | | TLS | 1.2+ only | TLS 1.0/1.1, SSLv3 | ## Critical Rules 1. **Never reuse IVs/nonces** — AES-GCM + repeated nonce = catastrophic 2. **Use authenticated encryption (AEAD)** — Plain CBC enables padding oracles 3. **Hash passwords, don't encrypt** — Hashing is one-way 4. **No hardcoded keys** — Use env vars, KMS, or Vault 5. **No Math.random() for crypto** — Use CSPRNG only 6. **Constant-time comparisons** — Prevent timing attacks on secrets 7. **Separate keys by purpose** — Encryption ≠ signing ≠ backup ## File Encryption (CLI) ```bash # age (modern, simple) age -p -o file.age file.txt age -d -o file.txt file.age # GPG gpg -c --cipher-algo AES256 file.txt ``` ## Platform-Specific See `patterns.md` for code snippets: - Password hashing (Node, Python, Go) - Envelope encryption with KMS - JWT with RS256 key rotation - Secure token generation See `mobile.md` for: - iOS Keychain wrapper - Android EncryptedSharedPreferences - SQLCipher setup - Biometric auth integration - Certificate pinning See `infra.md` for: - TLS certificate auto-renewal - HashiCorp Vault policies - mTLS between services - Backup encryption verification ## Audit Checklist - [ ] No plaintext passwords in DB/logs/env - [ ] No secrets in git history - [ ] No hardcoded keys in source - [ ] No Math.random() for security - [ ] No deprecated algorithms (MD5, SHA1, DES) - [ ] No disabled cert validation - [ ] IVs/nonces never reused - [ ] PBKDF2 iterations ≥600k / bcrypt cost ≥12 - [ ] TLS 1.2+ enforced, old protocols disabled - [ ] Key rotation procedure documented

path: infra.md
content: # Infrastructure Encryption Patterns

## TLS Certificate Auto-Renewal

### Certbot with systemd timer
```bash
# /etc/systemd/system/certbot-renewal.timer
[Unit]
Description=Certbot renewal timer

[Timer]
OnCalendar=*-*-* 02:00:00
RandomizedDelaySec=3600
Persistent=true

[Install]
WantedBy=timers.target
```

```bash
# /etc/systemd/system/certbot-renewal.service
[Unit]
Description=Certbot renewal

[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet --deploy-hook "systemctl reload nginx"
```

### Monitor expiry (cron check)
```bash
#!/bin/bash
DOMAIN="example.com"
DAYS_WARN=14

EXPIRY=$(echo | openssl s_client -servername $DOMAIN -connect $DOMAIN:443 2>/dev/null | \
         openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))

if [ $DAYS_LEFT -lt $DAYS_WARN ]; then
    echo "ALERT: $DOMAIN cert expires in $DAYS_LEFT days"
fi
```

## HashiCorp Vault Policies

### Least privilege service policy
```hcl
# policy: myapp-prod
path "secret/data/myapp/prod/*" {
  capabilities = ["read"]
}

path "database/creds/myapp-prod" {
  capabilities = ["read"]
}

# Deny everything else implicitly
```

### Create and assign policy
```bash
vault policy write myapp-prod myapp-prod.hcl
vault token create -policy=myapp-prod -period=24h
```

### Auto-unseal with AWS KMS
```hcl
# vault.hcl
seal "awskms" {
  region     = "eu-west-1"
  kms_key_id = "alias/vault-unseal"
}
```

## mTLS Between Services

### Generate CA and certs
```bash
# Create CA
openssl genrsa -out ca.key 4096
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt -subj "/CN=Internal CA"

# Generate service cert
openssl genrsa -out service.key 2048
openssl req -new -key service.key -out service.csr -subj "/CN=myservice.internal"
openssl x509 -req -in service.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
        -out service.crt -days 365 -sha256
```

### Nginx mTLS config
```nginx
server {
    listen 443 ssl;
    
    ssl_certificate /etc/nginx/certs/service.crt;
    ssl_certificate_key /etc/nginx/certs/service.key;
    
    # Require client cert
    ssl_client_certificate /etc/nginx/certs/ca.crt;
    ssl_verify_client on;
    
    # Modern TLS only
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
}
```

## Encrypted Backups

### Backup with age encryption
```bash
#!/bin/bash
BACKUP_KEY="age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
DATE=$(date +%Y%m%d)

# Dump and encrypt
pg_dump mydb | age -r $BACKUP_KEY -o backup-$DATE.sql.age

# Verify decryption works (test with first 1KB)
age -d -i /path/to/key backup-$DATE.sql.age | head -c 1024 > /dev/null
if [ $? -ne 0 ]; then
    echo "ALERT: Backup decryption verification failed!"
    exit 1
fi

# Upload to cold storage
aws s3 cp backup-$DATE.sql.age s3://backups-encrypted/
```

### Verify backup is actually encrypted
```bash
# Should NOT show readable SQL
head -c 100 backup-$DATE.sql.age | file -
# Expected: "data" not "ASCII text"
```

## Secret Rotation Script

```bash
#!/bin/bash
# Rotate database password with zero downtime

NEW_PASS=$(openssl rand -base64 32)

# 1. Add new password to database (keeps old valid)
psql -c "ALTER USER appuser WITH PASSWORD '$NEW_PASS';"

# 2. Update in Vault
vault kv put secret/myapp/db [REDACTED_SECRET]"

# 3. Rolling restart of app pods (they'll pick up new secret)
kubectl rollout restart deployment/myapp

# 4. After grace period, invalidate old password
sleep 300
# (old sessions will have reconnected by now)
```

## TLS Hardening Check

```bash
#!/bin/bash
# Quick TLS audit
DOMAIN=$1

# Check protocols
echo "=== Protocols ==="
nmap --script ssl-enum-ciphers -p 443 $DOMAIN | grep -E "TLSv|SSLv"

# Check certificate
echo "=== Certificate ==="
echo | openssl s_client -connect $DOMAIN:443 2>/dev/null | openssl x509 -noout -dates -subject -issuer

# Check headers
echo "=== Security Headers ==="
curl -sI https://$DOMAIN | grep -iE "strict-transport|content-security|x-frame"
```

## Compliance Evidence Generation

```bash
#!/bin/bash
# Generate SOC2/PCI-DSS evidence report

echo "# Encryption Evidence Report - $(date)" > evidence.md
echo "" >> evidence.md

echo "## TLS Configuration" >> evidence.md
echo "\`\`\`" >> evidence.md
testssl.sh --quiet --severity HIGH example.com >> evidence.md
echo "\`\`\`" >> evidence.md

echo "## Encryption at Rest" >> evidence.md
echo "### Database" >> evidence.md
psql -c "SHOW ssl;" >> evidence.md
echo "### Disk" >> evidence.md
lsblk -o NAME,FSTYPE,MOUNTPOINT | grep crypt >> evidence.md

echo "## Key Rotation Log (last 90 days)" >> evidence.md
vault kv metadata get -format=json secret/myapp | jq '.data.versions' >> evidence.md
```


path: mobile.md
content: # Mobile Encryption Patterns

## iOS Keychain Wrapper

```swift
import Security

class KeychainHelper {
    static func save(key: String, data: Data) -> Bool {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: key,
            kSecValueData as String: data,
            kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
        ]
        SecItemDelete(query as CFDictionary)
        return SecItemAdd(query as CFDictionary, nil) == errSecSuccess
    }
    
    static func load(key: String) -> Data? {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: key,
            kSecReturnData as String: true,
            kSecMatchLimit as String: kSecMatchLimitOne
        ]
        var result: AnyObject?
        SecItemCopyMatching(query as CFDictionary, &result)
        return result as? Data
    }
}
```

## Android EncryptedSharedPreferences

```kotlin
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey

val masterKey = MasterKey.Builder(context)
    .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
    .build()

val prefs = EncryptedSharedPreferences.create(
    context,
    "secure_prefs",
    masterKey,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)

// Use like regular SharedPreferences
prefs.edit().putString("token", secretToken).apply()
```

## SQLCipher Setup

### iOS (GRDB + SQLCipher)
```swift
var config = Configuration()
config.prepareDatabase { db in
    try db.usePassphrase(getKeyFromKeychain())
}
let dbQueue = try DatabaseQueue(path: dbPath, configuration: config)
```

### Android (SQLCipher + Room)
```kotlin
val passphrase = SQLiteDatabase.getBytes(getKeyFromKeystore())
val factory = SupportFactory(passphrase)
Room.databaseBuilder(context, AppDatabase::class.java, "app.db")
    .openHelperFactory(factory)
    .build()
```

## Biometric Authentication

### iOS Face ID / Touch ID
```swift
import LocalAuthentication

let context = LAContext()
var error: NSError?

if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
    context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics,
                          localizedReason: "Unlock secure data") { success, error in
        if success {
            // Release key from Keychain
        } else {
            // Fall back to PIN
        }
    }
}
```

### Android BiometricPrompt
```kotlin
val promptInfo = BiometricPrompt.PromptInfo.Builder()
    .setTitle("Unlock")
    .setNegativeButtonText("Use PIN")
    .setAllowedAuthenticators(BIOMETRIC_STRONG)
    .build()

val biometricPrompt = BiometricPrompt(this, executor,
    object : BiometricPrompt.AuthenticationCallback() {
        override fun onAuthenticationSucceeded(result: AuthenticationResult) {
            val cipher = result.cryptoObject?.cipher
            // Use cipher to decrypt key
        }
    })
biometricPrompt.authenticate(promptInfo, CryptoObject(cipher))
```

## Certificate Pinning

### iOS (URLSession)
```swift
class PinningDelegate: NSObject, URLSessionDelegate {
    let pinnedHash = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
    
    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge,
                   completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        guard let trust = challenge.protectionSpace.serverTrust,
              let cert = SecTrustGetCertificateAtIndex(trust, 0) else {
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }
        let pubKey = SecCertificateCopyKey(cert)!
        let pubKeyData = SecKeyCopyExternalRepresentation(pubKey, nil)! as Data
        let hash = SHA256.hash(data: pubKeyData).base64EncodedString()
        
        if hash == pinnedHash {
            completionHandler(.useCredential, URLCredential(trust: trust))
        } else {
            completionHandler(.cancelAuthenticationChallenge, nil)
        }
    }
}
```

### Android (network_security_config.xml)
```xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config>
        <domain includeSubdomains="true">api.example.com</domain>
        <pin-set expiration="2025-01-01">
            <pin digest="SHA-256">AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=</pin>
            <!-- Backup pin -->
            <pin digest="SHA-256">BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=</pin>
        </pin-set>
    </domain-config>
</network-security-config>
```


path: patterns.md
content: # Encryption Code Patterns

## Password Hashing

### Node.js (bcrypt)
```javascript
const bcrypt = require('bcrypt');
const COST = 12;

// Hash
const hash = await bcrypt.hash(password, COST);

// Verify
const valid = await bcrypt.compare(password, hash);
```

### Python (argon2)
```python
from argon2 import PasswordHasher
ph = PasswordHasher()

# Hash
hash = ph.hash(password)

# Verify
try:
    ph.verify(hash, password)
except VerifyMismatchError:
    return False
```

## Secure Token Generation

### Node.js
```javascript
const crypto = require('crypto');
const [REDACTED_SECRET]).toString('hex');
```

### Python
```python
import secrets
[REDACTED_SECRET])
```

## Field-Level Database Encryption

### Envelope pattern (Node + AWS KMS)
```javascript
const { KMS } = require('@aws-sdk/client-kms');
const crypto = require('crypto');

async function encryptField(plaintext, kmsKeyId) {
  // Generate DEK
  const dek = crypto.randomBytes(32);
  const iv = crypto.randomBytes(16);
  
  // Encrypt data with DEK
  const cipher = crypto.createCipheriv('aes-256-gcm', dek, iv);
  const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
  const tag = cipher.getAuthTag();
  
  // Encrypt DEK with KMS
  const kms = new KMS();
  const { CiphertextBlob } = await kms.encrypt({
    KeyId: kmsKeyId,
    Plaintext: dek
  });
  
  return {
    encryptedData: encrypted.toString('base64'),
    encryptedDek: Buffer.from(CiphertextBlob).toString('base64'),
    iv: iv.toString('base64'),
    tag: tag.toString('base64')
  };
}
```

## JWT with RS256

```javascript
const jwt = require('jsonwebtoken');
const fs = require('fs');

const privateKey = fs.readFileSync('private.pem');
const publicKey = fs.readFileSync('public.pem');

// Sign
const [REDACTED_SECRET] userId: 123 }, privateKey, {
  algorithm: 'RS256',
  expiresIn: '1h',
  keyid: 'key-2024-01'  // For rotation
});

// Verify
const decoded = jwt.verify(token, publicKey, {
  algorithms: ['RS256']
});
```

## Key Rotation (Zero Downtime)

```javascript
// Support multiple active keys
const KEYS = {
  'v2': { key: process.env.KEY_V2, active: true },
  'v1': { key: process.env.KEY_V1, active: false }  // Grace period
};

function encrypt(data) {
  const activeKey = Object.entries(KEYS).find(([_, v]) => v.active);
  return {
    keyVersion: activeKey[0],
    data: doEncrypt(data, activeKey[1].key)
  };
}

function decrypt({ keyVersion, data }) {
  const key = KEYS[keyVersion]?.key;
  if (!key) throw new Error('Unknown key version');
  return doDecrypt(data, key);
}
```

## Progressive Password Rehashing

```javascript
async function verifyAndUpgrade(password, hash) {
  // Try modern algorithm first
  if (hash.startsWith('$argon2')) {
    return argon2.verify(hash, password);
  }
  
  // Legacy: verify with old algorithm
  if (hash.startsWith('$2')) {  // bcrypt
    const valid = await bcrypt.compare(password, hash);
    if (valid) {
      // Upgrade to argon2 on successful login
      const newHash = await argon2.hash(password);
      await updateUserHash(userId, newHash);
    }
    return valid;
  }
  
  throw new Error('Unknown hash format');
}
```


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)