jenkins-decryptor
#!/usr/bin/env python3
import re
import sys
import base64
from hashlib import sha256
from Crypto.Cipher import AES
from pathlib import Path
MAGIC = b"::::MAGIC::::"
def load_secret(master_key_path: Path, hudson_secret_path: Path) -> bytes:
master_key = master_key_path.read_bytes()
hashed_master = sha256(master_key).digest()[:16]
hudson_secret = hudson_secret_path.read_bytes()
ecb = AES.new(hashed_master, AES.MODE_ECB)
decrypted = ecb.decrypt(hudson_secret)
secret = decrypted[:-16][:16]
return secret
def pkcs7_unpad(blocks: bytes) -> bytes:
pad_len = blocks[-1]
if pad_len <= 16:
return blocks[:-pad_len]
return blocks
def decrypt_new(secret: bytes, payload: bytes) -> str:
p = payload[1:]
iv_len = (p[0]<<24) | (p[1]<<16) | (p[2]<<8) | p[3]; p = p[4:]
data_len = (p[0]<<24) | (p[1]<<16) | (p[2]<<8) | p[3]; p = p[4:]
iv = p[:iv_len]; cipher = p[iv_len:]
aes = AES.new(secret, AES.MODE_CBC, iv)
decrypted = aes.decrypt(cipher)
plaintext = pkcs7_unpad(decrypted)
return plaintext.decode("utf-8", errors="replace")
def decrypt_old(secret: bytes, payload: bytes) -> str:
aes = AES.new(secret, AES.MODE_ECB)
decrypted = aes.decrypt(payload)
return decrypted.split(MAGIC)[0].decode("utf-8", errors="replace")
def find_encrypted_strings(xml_text: str) -> list[str]:
pattern = re.compile(
r"<(?:password|passphrase|privateKey)>\s*\{(.*?)\}\s*</(?:password|passphrase|privateKey)>",
re.DOTALL
)
return pattern.findall(xml_text)
def main():
if len(sys.argv) != 4:
print(f"Usage: {Path(sys.argv[0]).name} <master.key> <hudson.util.Secret> <credentials.xml>")
sys.exit(1)
secret = load_secret(Path(sys.argv[1]), Path(sys.argv[2]))
xml_text = Path(sys.argv[3]).read_text(encoding="utf-8", errors="ignore")
for b64 in find_encrypted_strings(xml_text):
payload = base64.b64decode(b64)
if payload and payload[0] == 1:
print(decrypt_new(secret, payload))
else:
print(decrypt_old(secret, payload))
if __name__ == "__main__":
main()