The Cipher Behind QSYRUPWD: Reconstructing IBM i Password Hashes - Silent Signal Techblog
Intro
QSYRUPWD (Retrieve Encrypted User Password) is an IBM i API that returns password-related<br>data for a specified user profile in encrypted form to authorized callers.<br>IBM i is IBM’s integrated enterprise platform, originating from the AS/400 line,<br>and is still widely used for business-critical workloads such as ERP, finance,<br>logistics, and manufacturing.<br>The API primarily exists to support system functions such as password synchronization,<br>migration, replication, and other administrative scenarios where password material<br>must be transferred without exposing the cleartext password. IBM documents QSYRUPWD<br>as part of its security-related API set for handling encrypted password data.
During one of our IBM i penetration tests, I noticed that the client’s server was<br>configured with QPWDLVL = 2. On IBM i, the QPWDLVL<br>system value determines both<br>the password rules accepted by the operating system and the verifier formats maintained<br>for authentication compatibility. IBM documents that password levels 0 and 1 correspond<br>to the older DES-based scheme, levels 2 and 3 use a SHA-1-based scheme,<br>and level 4<br>introduces a PBKDF2-based verifier model. IBM also states that when QPWDLVL<br>is set to 2, the operating system keeps compatibility with multiple password-verifier<br>forms rather than removing earlier ones.
This distinction is important when interpreting QSYRUPWD output.<br>John the Ripper includes dedicated IBM i cracking formats for legacy IBM i material,<br>specifically as400_des and as400_ssha1, which reflects support for the older<br>DES-based and salted SHA-1-based representations. However, in our testing, the QSYRUPWD<br>output obtained from systems running at QPWDLVL 2–4 did not match the form expected by<br>John the Ripper’s IBM i formats, whereas the legacy QPWDLVL 0–1 case is compatible with<br>the QSYRUPWD-derived material handled by those modules. In other words, for QPWDLVL 2–4,<br>the content returned by QSYRUPWD changes in a way that makes direct use with the existing<br>John the Ripper IBM i formats unsuccessful. This is an empirical observation from our<br>testing and tooling analysis, not a claim that IBM publicly documents the exact returned<br>structure for those levels.
As a result, even with *ALLOBJ and *SECADM (which are required to call QSYRUPWD), the API<br>could no longer be used for practical password-strength testing. After this engagement,<br>I decided to analyze the QSYRUPWD API in detail.
Tools
To examine the API behavior directly, I wrote a small CL program that invokes<br>QSYRUPWD with the UPWD0100 format, retrieves the returned buffer, and emits<br>it for further analysis.<br>The goal was not to build a full extraction tool, but to verify what kind of<br>password-related data the API returns under different QPWDLVL settings.<br>This made it possible to compare the actual output format across systems and<br>determine whether the returned data matched the input expected by existing<br>cracking tools such as John the Ripper.
PGM PARM(&USER)<br>DCL VAR(&USER) TYPE(*CHAR) LEN(10)<br>DCL VAR(&RCV) TYPE(*CHAR) LEN(4000)<br>DCL VAR(&LEN) TYPE(*INT) LEN(4) VALUE(4000)<br>DCL VAR(&FMT) TYPE(*CHAR) LEN(8) VALUE('UPWD0100')<br>DCL VAR(&ERR) TYPE(*CHAR) LEN(8) VALUE(X'0000000000000000')<br>DCL VAR(&N) TYPE(*INT) LEN(4)<br>DCL VAR(&N4) TYPE(*CHAR) LEN(4)<br>DCL VAR(&NCH) TYPE(*CHAR) LEN(11)
CALL PGM(QSZS/QSYRUPWD) PARM(&RCV &LEN &FMT &USER &ERR)<br>MONMSG CPF0000 EXEC(DO)<br>SNDPGMMSG MSGID(CPF9898) MSGF(QCPFMSG) MSGDTA('QSYRUPWD failed') MSGTYPE(*ESCAPE)<br>RETURN<br>ENDDO
CHGVAR VAR(&N4) VALUE(%SST(&RCV 1 4))<br>CHGVAR VAR(&N) VALUE(%BIN(&N4))<br>CHGVAR VAR(&NCH) VALUE(%CHAR(&N))<br>SNDPGMMSG MSGID(CPF9898) MSGF(QCPFMSG) MSGDTA('QSYRUPWD ok, bytes:' *BCAT &NCH) MSGTYPE(*INFO)<br>SNDPGMMSG MSGID(CPF9898) MSGF(QCPFMSG) MSGDTA('Data: ' *BCAT &RCV) MSGTYPE(*INFO)<br>ENDPGM
Trace
After I compiled the PWDDUMP program, I wondered whether we could<br>generate a call trace. Luckily, IBM i provides a tool called STRTRC that can be<br>used to collect evidence about the calls made within QSYRUPWD.<br>So I logged in with a user that has *ALLOBJ and *SECADM authority<br>(required to run PWDDUMP), and ran the following commands:
STRTRC SSNID(MYTRACE) JOB(*ALL/QSECOFR/QPADEV0005) JOBTRCTYPE(*ALL)<br>CALL USERB1/PWDDUMP USERB1<br>ENDTRC SSNID(MYTRACE) PRTTRC(*YES)<br>PRTTRC DTALIB(QGPL) DTAMBR(MYTRACE)
These commands create a spool file with the required call-trace information,<br>but the structure is a bit confusing at first glance, here is a sample<br>snippet of the spool file:
| | SYNCHRONOU<br>CALL | CPU | READS | WR
TIME THREAD FLAG FUNCTION PROGRAM LIBRARY ENTRY EXIT LVL | TIME | DB NDB| DB<br>12:53:35.829041 00000065 CALL QCMD QSYS x000519 x000519 1 .000000 0 0 0<br>12:53:35.829041 00000065 CALL QUICMENU QSYS x0000C1 x0000C1 2 .000000 0 0 0<br>12:53:35.829041 00000065 CALL QUIMNDRV QSYS x00061E x00061E 3 .000000 0 0 0<br>12:53:35.829041 00000065 CALL QUIMGFLW QSYS x0004D9 x0004D9 4 .000000 0 0 0<br>12:53:35.829041 00000065 CALL QUICMD QSYS x00056F...