| name | perseus-crypto |
| description | Deep-dive cryptography and secrets analysis (JWT, hashing, encryption, key management) |
Perseus Crypto Specialist
Context & Authorization
IMPORTANT: This skill performs cryptographic security analysis on the user's own codebase. This is defensive security testing to find crypto weaknesses before they lead to data breaches.
Authorization: The user owns this codebase and has explicitly requested this specialized analysis.
Multi-Language Support
| Language | Libraries |
|---|
| JavaScript/TypeScript | jsonwebtoken, jose, bcrypt, crypto, node-forge |
| Go | golang.org/x/crypto, crypto/*, jwt-go, golang-jwt |
| PHP | openssl, password_hash, sodium, firebase/php-jwt |
| Python | PyJWT, cryptography, bcrypt, passlib, hashlib |
| Rust | jsonwebtoken, ring, rust-crypto, argon2, bcrypt |
| Java | jjwt, Bouncy Castle, Java Cryptography Architecture |
| Ruby | jwt, bcrypt, rbnacl, openssl |
| C# | System.IdentityModel.Tokens.Jwt, BCrypt.Net |
Overview
This specialist skill performs comprehensive cryptographic analysis including JWT security, hashing, encryption, and key management across all major languages.
When to Use: After /scan identifies JWT usage, password hashing, encryption, or secrets handling.
Goal: Ensure cryptographic implementations follow security best practices.
Engagement Mode Compatibility
| Mode | Specialist Behavior |
|---|
PRODUCTION_SAFE | Configuration and implementation analysis, minimal runtime checks |
STAGING_ACTIVE | Controlled token/crypto validation with throttling |
LAB_FULL | Extensive verification of crypto edge cases in lab |
LAB_RED_TEAM | Adversarial misuse simulation on test identities and synthetic keys |
Safety Gates (Required)
- Read
deliverables/engagement_profile.md before any runtime validation.
- Default to
PRODUCTION_SAFE if mode is unspecified.
- Enforce kill-switch thresholds from engagement profile.
- Never expose real secrets, keys, or live credentials in outputs.
Cryptographic Issues Covered
| Category | Issues | Impact |
|---|
| JWT | Algorithm confusion, weak secrets, missing validation | Auth bypass |
| Hashing | MD5/SHA1 for passwords, missing salt, weak iterations | Credential theft |
| Encryption | Weak ciphers, ECB mode, hardcoded keys | Data exposure |
| Random | Predictable RNG, weak seeds | Token prediction |
| Key Management | Hardcoded keys, insecure storage | Full compromise |
Execution Instructions
Step 0: Mode & Scope Alignment
- Load mode/scope/limits from
deliverables/engagement_profile.md.
- Respect
deliverables/verification_scope.md when present.
- Keep production checks passive-first and evidence-based.
Phase 1: JWT Analysis (4 Parallel Agents)
-
JWT Algorithm Analyst:
- "Find all JWT verification code. Check for algorithm validation."
Language-Specific Patterns:
jwt.verify(token, secret);
jwt.verify(token, secret, { algorithms: ['HS256'] });
token, _ := jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) {
return secret, nil
})
token, _ := jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) {
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected method: %v", t.Header["alg"])
}
return secret, nil
})
jwt.decode(token, secret)
jwt.decode(token, secret, algorithms=['HS256'])
JWT::decode($token, $key);
JWT::decode($token, new Key($key, 'HS256'));
decode::<Claims>(&token, &DecodingKey::from_secret(secret), &Validation::new(Algorithm::HS256))
-
JWT Secret Analyst:
- "Find JWT signing secrets across languages."
Patterns:
const secret = 'secret123';
const secret = 'password';
jwt.sign(payload, 'my-super-secret-key');
const secret = process.env.JWT_SECRET;
var jwtSecret = []byte("weak-secret")
var jwtSecret = []byte(os.Getenv("JWT_SECRET"))
SECRET_KEY = "secret"
SECRET_KEY = os.environ.get("JWT_SECRET")
-
JWT Claims Analyst:
- "Analyze JWT claim validation."
Required Validations:
| Claim | Purpose | Check |
|---|
| exp | Expiration | Token not expired |
| iat | Issued At | Not issued in future |
| nbf | Not Before | Token is active |
| iss | Issuer | Trusted issuer |
| aud | Audience | Intended recipient |
-
JWT Key Management Analyst:
- "Check RS256/ES256 key handling."
Issues:
- Private key in repository
- Key without rotation
- Public key as HMAC secret (algorithm confusion)
Phase 2: Password Hashing Analysis (3 Parallel Agents)
-
Hash Algorithm Analyst:
- "Find all password hashing across languages."
Language-Specific Patterns:
crypto.createHash('md5').update(password).digest('hex');
crypto.createHash('sha1').update(password).digest('hex');
crypto.createHash('sha256').update(password).digest('hex');
await bcrypt.hash(password, 12);
await argon2.hash(password);
md5.Sum([]byte(password))
sha256.Sum256([]byte(password))
bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
argon2.IDKey([]byte(password), salt, 1, 64*1024, 4, 32)
hashlib.md5(password.encode()).hexdigest()
hashlib.sha256(password.encode()).hexdigest()
bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))
from passlib.hash import argon2
argon2.hash(password)
md5($password);
sha1($password);
hash('sha256', $password);
password_hash($password, PASSWORD_ARGON2ID);
password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
bcrypt::hash(password, bcrypt::DEFAULT_COST)?;
argon2::hash_encoded(password.as_bytes(), &salt, &config)?;
MessageDigest.getInstance("MD5").digest(password.getBytes());
BCrypt.hashpw(password, BCrypt.gensalt(12));
-
Hash Comparison Analyst:
- "Check for timing-safe comparison."
Patterns:
if (storedHash === computedHash) { ... }
crypto.timingSafeEqual(Buffer.from(storedHash), Buffer.from(computedHash))
if storedHash == computedHash { ... }
subtle.ConstantTimeCompare([]byte(storedHash), []byte(computedHash))
if stored_hash == computed_hash: ...
hmac.compare_digest(stored_hash, computed_hash)
-
Password Policy Analyst:
- "Check password strength enforcement."
Phase 3: Encryption Analysis (4 Parallel Agents)
-
Cipher Selection Analyst:
- "Find all encryption operations."
Vulnerable Ciphers:
| Cipher | Status | Use Instead |
|---|
| DES | Broken | AES-256-GCM |
| 3DES | Deprecated | AES-256-GCM |
| RC4 | Broken | AES-256-GCM |
| Blowfish | Weak | AES-256-GCM |
| AES-ECB | Insecure | AES-256-GCM |
| AES-CBC | OK (with HMAC) | AES-256-GCM preferred |
Language Patterns:
crypto.createCipher('des', key);
crypto.createCipheriv('aes-128-ecb', key, '');
crypto.createCipheriv('aes-256-gcm', key, iv);
des.NewCipher(key)
cipher.NewCBCEncrypter(block, iv)
cipher.NewGCM(block)
from Crypto.Cipher import DES
cipher = AES.new(key, AES.MODE_ECB)
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
-
IV/Nonce Analyst:
- "Check IV/nonce generation."
Issues:
const iv = Buffer.from('0000000000000000', 'hex');
const iv = Buffer.from(Date.now().toString());
const iv = crypto.randomBytes(16);
-
Key Derivation Analyst:
- "Check key derivation from passwords."
Patterns:
const key = Buffer.from(password);
crypto.pbkdf2Sync(password, salt, 100000, 32, 'sha256');
crypto.scryptSync(password, salt, 32);
-
Key Management Analyst:
- "Find encryption key storage issues."
Phase 4: Random Number Analysis (2 Parallel Agents)
-
PRNG Analyst:
- "Find insecure random number generation."
Language-Specific:
Math.random()
crypto.randomBytes(32)
crypto.randomUUID()
math/rand.Int()
crypto/rand.Read(buf)
random.random()
random.randint()
secrets.token_bytes(32)
secrets.token_hex(32)
secrets.token_urlsafe(32)
rand()
mt_rand()
random_bytes(32)
random_int(0, PHP_INT_MAX)
use rand::rngs::OsRng;
let random: u64 = OsRng.gen();
new Random().nextInt()
new SecureRandom().nextInt()
-
Token Generation Analyst:
- "Check security token generation."
Phase 5: Secrets in Code (3 Parallel Agents)
-
Hardcoded Secrets Scanner:
- "Deep scan for hardcoded secrets."
Patterns:
# AWS
AKIA[0-9A-Z]{16}
# GitHub
ghp_[a-zA-Z0-9]{36}
github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}
# Stripe
sk_live_[a-zA-Z0-9]{24}
rk_live_[a-zA-Z0-9]{24}
# Private Keys
-----BEGIN (RSA|EC|OPENSSH|PGP) PRIVATE KEY-----
# Generic
(password|secret|key|token|api_key)\s*[:=]\s*['\"][^'\"]+['\"]
-
Secret Exposure Analyst:
- "Check where secrets might leak."
Locations:
- Log files
- Error messages
- API responses
- Client-side code
- Git history
-
Environment Variable Analyst:
- "Check .env file security."
Issues:
- .env in repository
- .env.example with real secrets
- Missing .env in .gitignore
Output Requirements
Create deliverables/crypto_security_analysis.md:
# Cryptographic Security Analysis
## Summary
| Category | Issues | Critical | High | Medium |
|----------|--------|----------|------|--------|
| JWT | X | Y | Z | W |
| Hashing | X | Y | Z | W |
| Encryption | X | Y | Z | W |
| Random | X | Y | Z | W |
| Secrets | X | Y | Z | W |
## Language/Framework Detected
- Primary: [e.g., Node.js, Go, Python]
- Crypto Libraries: [e.g., crypto, bcrypt, jose]
## JWT Security Status
| Check | Status | Details |
|-------|--------|---------|
| Algorithm Validation | FAIL | Accepts 'none' algorithm |
| Secret Strength | FAIL | 8 character secret |
| Expiration | PASS | 1 hour expiry enforced |
| Issuer Validation | WARN | Not validated |
## Critical Findings
### [CRYPTO-001] JWT Algorithm Confusion
**Severity:** Critical
**Language:** Node.js
**Location:** `middleware/auth.js:23`
**Vulnerable Code:**
```javascript
const decoded = jwt.verify(token, publicKey);
Attack:
- Take valid RS256 token
- Change header to HS256
- Sign with public key as secret
- Server verifies with public key as HMAC secret
Remediation:
const decoded = jwt.verify(token, publicKey, {
algorithms: ['RS256']
});
[CRYPTO-002] MD5 Password Hashing
Severity: Critical
Language: Python
Location: models/user.py:45
Vulnerable Code:
hashed = hashlib.md5(password.encode()).hexdigest()
Remediation:
import bcrypt
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))
Password Hashing Status
| Language | Algorithm | Cost/Rounds | Status |
|---|
| Node.js | bcrypt | 10 | WARN (use 12+) |
| Python | MD5 | N/A | CRITICAL |
| Go | bcrypt | 14 | OK |
Encryption Status
| Usage | Cipher | Mode | Status |
|---|
| File encryption | AES-256 | ECB | CRITICAL |
| API encryption | AES-128 | GCM | WARN (use 256) |
Random Number Generation
| Usage | Method | Status |
|---|
| Session tokens | Math.random() | CRITICAL |
| Password reset | crypto.randomBytes() | OK |
Hardcoded Secrets Found
| Type | Location | Severity |
|---|
| AWS Access Key | config/aws.js:3 | Critical |
| JWT Secret | auth/jwt.js:5 | Critical |
| Database Password | .env.example:8 | High |
Recommendations
Immediate Actions
- Implement algorithm validation for JWT
- Migrate password hashing to Argon2id or bcrypt (cost 12+)
- Move all secrets to environment variables
- Replace Math.random() with crypto.randomBytes()
Hashing Migration Guide
const hash = md5(password);
const hash = await bcrypt.hash(password, 12);
const hash = await argon2.hash(password, {
type: argon2.argon2id,
memoryCost: 65536,
timeCost: 3,
parallelism: 4
});
**Next Step:** JWT vulnerabilities can be verified with crafted tokens.