/// <summary>Encrypts password using IBM's flavor of DES algorithm as defined in RFC2877</summary> /// <param name="userName">User name in ASCII</param> /// <param name="password">Password in ASCII</param> /// <param name="serverSeed">Server's seed</param> /// <param name="clientSeed">Client's seed</param> /// <returns>Encrypted password as EBCDIC byte stream</returns> public static byte[] EncryptPasswordDES(string userName, string password, ulong serverSeed, ulong clientSeed) { byte[] passwordToken = new byte[8]; if (password.Length > 8) { byte[] passwordTokenA = GenerateToken(userName, password.Substring(0, 8)); byte[] passwordTokenB = GenerateToken(userName, password.Substring(8)); passwordToken = Converters.UInt64ToBigEndian(Converters.BigEndianToUInt64(passwordTokenA) ^ Converters.BigEndianToUInt64(passwordTokenB)); } else { passwordToken = GenerateToken(userName, password); } byte[] usernameEBCDIC_A; byte[] usernameEBCDIC_B; if (userName.Length <= 8) { usernameEBCDIC_A = Converters.AsciiToEbcdic(userName.ToUpper().PadRight(8)); usernameEBCDIC_B = Converters.UInt64ToBigEndian(0x4040404040404040); } else { usernameEBCDIC_A = Converters.AsciiToEbcdic(userName.Substring(0, 8).ToUpper().PadRight(8)); usernameEBCDIC_B = Converters.AsciiToEbcdic(userName.Substring(8).ToUpper().PadRight(8)); } byte[] firstEncryptionRound = EncryptDES(Converters.UInt64ToBigEndian(serverSeed + 1), passwordToken); byte[] secondEncryptionRound = EncryptDES(Converters.UInt64ToBigEndian(Converters.BigEndianToUInt64(firstEncryptionRound) ^ clientSeed), passwordToken); byte[] thirdEncryptionRound = EncryptDES(Converters.UInt64ToBigEndian(Converters.BigEndianToUInt64(usernameEBCDIC_A) ^ (serverSeed + 1) ^ Converters.BigEndianToUInt64(secondEncryptionRound)), passwordToken); byte[] fourthEncryptionRound = EncryptDES(Converters.UInt64ToBigEndian(Converters.BigEndianToUInt64(usernameEBCDIC_B) ^ (serverSeed + 1) ^ Converters.BigEndianToUInt64(thirdEncryptionRound)), passwordToken); return(EncryptDES(Converters.UInt64ToBigEndian(Converters.BigEndianToUInt64(fourthEncryptionRound) ^ 0x0000000000000001), passwordToken)); }
/// <summary>Creates an intermediary password token using DES algorithm</summary> /// <param name="userName">User name in ASCII</param> /// <param name="password">Password in ASCII</param> /// <returns>Encrypted password token as EBCDIC byte stream</returns> private static byte[] GenerateToken(string userName, string password) { if (password.Length > 8) { throw new System.InvalidOperationException("Wrong method invocation: password cannot be longer than 8"); } if (userName.Length > 10) { throw new System.InvalidOperationException("Wrong method invocation: user name cannot be longer than 10"); } byte[] passwordEBCDIC = Converters.AsciiToEbcdic(password.ToUpper().PadRight(8)); byte[] encryptionKey = Converters.UInt64ToBigEndian((Converters.BigEndianToUInt64(passwordEBCDIC) ^ 0x5555555555555555) << 1); byte[] usernameEBCDIC = PrepareUserNameDES(userName); return(EncryptDES(usernameEBCDIC, encryptionKey)); }
//----------------------------------------------------------------------- // Private methods //----------------------------------------------------------------------- /// <summary>Connects to the Sign-on Verify server</summary> private void ConnectToSignonVerifyServer() { // Establish authentication channel this.socketConnectorSignonVerify = new SocketConnector(this.serverName, this.useSSL ? TcpPortSignonVerifySSL : TcpPortSignonVerify, this.useSSL, this.ignoreSelfSignedCertificates); // Default current seed information this.clientSeed = 0; this.serverSeed = 0; // Exchange random seeds ulong clientSeed = (ulong)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds; BigEndianMemoryStream outputStream = new BigEndianMemoryStream(); //// outputStream.WriteInt(52); // length outputStream.WriteShort(0); // Header ID (0) outputStream.WriteShort(ServerIDSignonVerify); // Server ID outputStream.WriteInt(0); // CS instance outputStream.WriteInt(0); // Correlation ID outputStream.WriteShort(0); // Template length outputStream.WriteShort(0x7003); // ReqReP ID outputStream.WriteInt(10); // Client version LL outputStream.WriteShort(0x1101); // Client version CP outputStream.WriteInt(1); // Client version outputStream.WriteInt(8); // Client data stream level LL outputStream.WriteShort(0x1102); // Client datastream level CP outputStream.WriteShort(2); // Client datastream level outputStream.WriteInt(14); // Client seed LL outputStream.WriteShort(0x1103); // Client seed CP outputStream.WriteLong(clientSeed); // Client seed this.socketConnectorSignonVerify.Write(outputStream.ToArray()); // Retrieve server response byte[] response = this.socketConnectorSignonVerify.Read(); BigEndianMemoryStream inputStream = new BigEndianMemoryStream(); inputStream.Write(response, 0, response.Length); /* * The response comes in the following format: * * Offset (byte) Information Length (byte) * ----------------------------------------------------------- * Fixed header * ----------------------------------------------------------- * 0 Response length 4 * 4 (Reserved) 16 * 20 Result code 4 * 24 (Dynamic fields) see below * ----------------------------------------------------------- * Dynamic fields (Offset is dynamic) * ----------------------------------------------------------- * 0 Field length 4 * 4 Field code 2 * 6 Field data (Field length - 6) */ // Read fixed header inputStream.Position = 0; uint responseLength = inputStream.ReadInt(); if (responseLength < 20) { throw new System.InvalidOperationException("Seeds exchange failed. Bad response length."); } inputStream.Position += 16; uint resultCode = inputStream.ReadInt(); if (resultCode != 0) { throw new System.InvalidOperationException("Seeds exchange failed. Bad return code."); } while (inputStream.Position < responseLength) { uint dynamicFieldLength = inputStream.ReadInt(); ushort dynamicFieldCode = inputStream.ReadShort(); byte[] dynamicFieldData = new byte[dynamicFieldLength - 6]; inputStream.Read(dynamicFieldData, 0, (int)dynamicFieldLength - 6); switch (dynamicFieldCode) { case 0x1101: // Server Version this.serverVersion = Converters.BigEndianToUInt32(dynamicFieldData); break; case 0x1102: // Server Level this.serverLevel = Converters.BigEndianToUInt16(dynamicFieldData); break; case 0x1103: // Server Seed this.serverSeed = Converters.BigEndianToUInt64(dynamicFieldData); break; case 0x1119: // Password Level this.passwordLevel = Converters.BigEndianToUInt8(dynamicFieldData); break; case 0x111F: // Job Name this.jobName = Converters.EbcdicToAsciiString(dynamicFieldData, 4); break; default: break; } } Debug.WriteLine("Seeds were exchanged, return code is 0x" + resultCode.ToString("X8")); this.clientSeed = clientSeed; }
/// <summary>Reads a long value from the stream</summary> /// <returns>A long value</returns> public ulong ReadLong() { byte[] longArray = new byte[8]; this.Read(longArray, 0, 8); return(Converters.BigEndianToUInt64(longArray)); }
//----------------------------------------------------------------------- // Class methods //----------------------------------------------------------------------- /// <summary>Returns password hash of the specified user, in specified format</summary> /// <param name="system">System to be connected to</param> /// <param name="userName">User name</param> /// <param name="hashType">Predefined password hash type (see documentation for details)</param> /// <returns>Password hash as a hex string</returns> public string GetEncryptedPassword(string userName, int hashType) { /* * http://publib.boulder.ibm.com/infocenter/iseries/v5r4/index.jsp?topic=%2Fapis%2Fqsyrupwd.htm * * Required Parameter Group: * * 1 Receiver variable Output Char(*) - 2000B * 2 Length of receiver variable Input Binary(4) * 3 Format Input Char(8) - "UPWD0100" * 4 User profile name Input Char(10) - userName * 5 Error code I/O Char(*) */ ProgramCallParameters qsyrupwdCallParameters = new ProgramCallParameters(5) { [0] = new ProgramCallParameter( ProgramCallParameter.ParameterTypeOutput, null, 2000), [1] = new ProgramCallParameter( ProgramCallParameter.ParameterTypeInput, Converters.UInt32ToBigEndian(2000)), [2] = new ProgramCallParameter( ProgramCallParameter.ParameterTypeInput, Converters.AsciiToEbcdic("UPWD0100")), [3] = new ProgramCallParameter( ProgramCallParameter.ParameterTypeInput, Converters.AsciiToEbcdic(userName.ToUpper().PadRight(10))), [4] = new ProgramCallParameter( ProgramCallParameter.ParameterTypeInputOutput, null, 500) }; CallMessages qsyrupwdCallMessages = new CallMessages(); if (CallProgram("QSYRUPWD", "QSYS", ref qsyrupwdCallParameters, ref qsyrupwdCallMessages) != 0) { foreach (CallMessage outputMessage in qsyrupwdCallMessages) { Debug.WriteLine(outputMessage.MessageText); } throw new System.InvalidOperationException("The method GetEncryptedPassword failed. Check debug information."); } switch (hashType) { case PASSWORD_HASH_ALLDATA: // All data return(Converters.BigEndianToHexString(qsyrupwdCallParameters[0].ParameterValue, 1, 269)); case PASSWORD_HASH_UNKNOWNHASH: // Unknown (hash?) data return(Converters.BigEndianToHexString(qsyrupwdCallParameters[0].ParameterValue, 78, 192)); case PASSWORD_HASH_HMACSHA1MC: // HMAC-SHA1 password (mixed case) return(Converters.BigEndianToHexString(qsyrupwdCallParameters[0].ParameterValue, 35, 20)); case PASSWORD_HASH_HMACSHA1UC: // HMAC-SHA1 password (uppercase) return(Converters.BigEndianToHexString(qsyrupwdCallParameters[0].ParameterValue, 55, 20)); case PASSWORD_HASH_LMHASH: // LM hash return(Converters.BigEndianToHexString(qsyrupwdCallParameters[0].ParameterValue, 17, 16)); case PASSWORD_HASH_DES: // Composed DES hash (PW_TOKENa XOR PW_TOKENb): return(Converters.BigEndianToHexString(Converters.UInt64ToBigEndian(Converters.BigEndianToUInt64(qsyrupwdCallParameters[0].ParameterValue, 1, 8) ^ Converters.BigEndianToUInt64(qsyrupwdCallParameters[0].ParameterValue, 9, 8)))); case PASSWORD_HASH_SECONDDES: // Second DES password token (PW_TOKENb) return(Converters.BigEndianToHexString(qsyrupwdCallParameters[0].ParameterValue, 9, 8)); case PASSWORD_HASH_FIRSTDES: // First DES password (PW_TOKENa) default: return(Converters.BigEndianToHexString(qsyrupwdCallParameters[0].ParameterValue, 1, 8)); } }