//#pragma title( "PwGen.cpp - PasswordGenerate implementation" ) /* Copyright (c) 1995-1998, Mission Critical Software, Inc. All rights reserved. =============================================================================== Module - PwGen.cpp System - EnterpriseAdministrator Author - Steven Bailey, Marcus Erickson Created - 1997-05-30 Description - PasswordGenerate implementation Updates - =============================================================================== */ #include #include #include #include #include #include "Common.hpp" #include "Err.hpp" #include "UString.hpp" #include "pwgen.hpp" int iRand(int iMin, int iMax); void __stdcall GenerateRandom(DWORD dwCount, BYTE* pbRandomType, BYTE* pbRandomChar); ///////////////////////////////////////////////////////////////////////////////////////////////////////// // Generate a password from the rules provided. // Returns ERROR_SUCCESS if successful, else ERROR_INVALID_PARAMETER. // If successful, the new password is returned in the supplied buffer. // The buffer must be long enough to hold the minimum length password // that is required by the rules, plus a terminating NULL. DWORD __stdcall // ret-EA/OS return code EaPasswordGenerate( DWORD dwMinUC, // in -minimum upper case chars DWORD dwMinLC, // in -minimum lower case chars DWORD dwMinDigits, // in -minimum numeric digits DWORD dwMinSpecial, // in -minimum special chars DWORD dwMaxConsecutiveAlpha,// in -maximum consecutive alpha chars DWORD dwMinLength, // in -minimum length WCHAR * newPassword, // out-returned password DWORD dwBufferLength // in -returned area buffer length ) { DWORD dwMaxLength = PWGEN_MAX_LENGTH; DWORD dwNewLength; // actual length of new password DWORD dwUC = dwMinUC; // actual numbers of these characters DWORD dwLC = dwMinLC; DWORD dwDigits = dwMinDigits; DWORD dwSpecial = dwMinSpecial; DWORD dwActualLength = dwUC + dwLC + dwDigits + dwSpecial; // total length specified by the minima TCHAR pszNewPassword[PWGEN_MAX_LENGTH+1]; // out-returned password BYTE bRandomType[PWGEN_MAX_LENGTH]; // cryptographically generated random bytes for type BYTE bRandomChar[PWGEN_MAX_LENGTH]; // cryptographically generated random bytes for character const TCHAR *szSourceString[4] = { // the lists of characters by type { TEXT("ABDEFGHJKLMNQRTY") }, { TEXT("abcdefghkmnpqrstuvwxyz") }, { TEXT("23456789") }, { TEXT("~!@#$%^+=") } }; DWORD dwToPlace[4]; // number of characters of a type to place int iType[4]; // type of each character class int iTypes; // total number of types enum { // types of chars eUC = 0, eLC, eDigit, eSpecial }; // Sanity checking // Does the minimum passed to us exceed the maximum? if (dwMinLength > dwMaxLength) return ERROR_INVALID_PARAMETER; // Adjust the minimum length dwMinLength = max(dwMinLength, dwMinUC + dwMinLC + dwMinDigits + dwMinSpecial); dwMinLength = max(dwMinLength, PWGEN_MIN_LENGTH); // Do the minimum requirements make the password too long? if ((dwMinUC + dwMinLC + dwMinDigits + dwMinSpecial) > dwMaxLength) return ERROR_INVALID_PARAMETER; // Adjust maximum length to size of buffer. dwMaxLength = min(dwMaxLength, dwBufferLength - 1); // Do the min LC and UC characters make it impossible to satisfy the maximum consecutive alpha characters? if (dwMaxConsecutiveAlpha) { if (dwMaxLength - dwMaxLength / (dwMaxConsecutiveAlpha + 1) < (dwMinUC + dwMinLC)) return ERROR_INVALID_PARAMETER; } // Adjust the minimum length to accomodate the rules about max consecutive alphas. if (dwMaxConsecutiveAlpha) { DWORD dwTotalAlpha = dwUC + dwLC; if (dwTotalAlpha) { DWORD dwMinGroups = dwTotalAlpha / dwMaxConsecutiveAlpha; // we need at least this minus one separators if (dwTotalAlpha % dwMaxConsecutiveAlpha) ++dwMinGroups; dwMinLength = max(dwMinLength, dwTotalAlpha + dwMinGroups - 1); } } // Check confirmed min length against maximum length. if (dwMinLength > dwMaxLength) return ERROR_INVALID_PARAMETER; // Seed the random-number generator with current time so that // the numbers will be different every time we run. #ifndef _DEBUG // Note for debugging: If this is run in a tight loop, the tick count // won't be incrementing between calls, so the same password will generate // repeatedly. That doesn't help you test anything. srand( (int)GetTickCount() ); #endif // Determine the actual length of new password. dwNewLength = dwMinLength; // Adjust max consecutive alpha if (dwMaxConsecutiveAlpha == 0) dwMaxConsecutiveAlpha = dwNewLength; // Determine the actual numbers of each type of character. if (dwActualLength < dwNewLength) { // Try to pad with alphabetic characters. // Determine the maximum number of alpha characters that could be added. int iAddAlpha = (int)(dwNewLength - dwNewLength / (dwMaxConsecutiveAlpha + 1) - (dwUC + dwLC)); // It cannot exceed the number of characters we need. if ((DWORD)iAddAlpha > (dwNewLength - dwActualLength)) iAddAlpha = (int)(dwNewLength - dwActualLength); dwLC += (DWORD)iAddAlpha; dwActualLength += (DWORD)iAddAlpha; } // Make certain there are enough groups. if (dwActualLength < dwNewLength) // The padding is separators. dwDigits += dwNewLength - dwActualLength; // Prepare to generate the characters. dwToPlace[0] = dwUC; dwToPlace[1] = dwLC; dwToPlace[2] = dwDigits; dwToPlace[3] = dwSpecial; iType[0] = eUC; iType[1] = eLC; iType[2] = eDigit; iType[3] = eSpecial; iTypes = 4; for (int iPos = 0; iPos < iTypes; ) { if (!dwToPlace[iPos]) { for (int iNextPos = iPos + 1; iNextPos < iTypes; ++iNextPos) { dwToPlace[iNextPos - 1] = dwToPlace[iNextPos]; iType[iNextPos - 1] = iType[iNextPos]; } --iTypes; } else ++iPos; } // Result: dwToPlace[0..iTypes - 1] contain all non-zero values; // iType[0..iTypes - 1] contain the type of character they represent. // generate cryptographically random bytes // for choosing both the character type and character GenerateRandom(dwNewLength, bRandomType, bRandomChar); // Generate a string. DWORD dwConsecAlpha = 0; int iRemainingAlpha = (int)(dwUC + dwLC); int iTypeList[PWGEN_MAX_LENGTH]; // A distributed list of types. for (int iNewChar = 0; (DWORD)iNewChar < dwNewLength; ++iNewChar) { // Determine whether the next char must be alpha or must not be alpha. BOOL bMustBeAlpha = FALSE; BOOL bMustNotBeAlpha = dwConsecAlpha == dwMaxConsecutiveAlpha; // If it can be alpha, determine whether it HAS to be alpha. if (!bMustNotBeAlpha) { // If, among the remaining chars after this one, it would be impossible to // fit the remaining alpha chars due to constraints of dwMaxConsecutiveAlpha, // then this character must be alpha. // Determine the minimum number of groups if we put remaining alpha chars // into groups that are the maximum width. int iMinGroups = iRemainingAlpha / (int)dwMaxConsecutiveAlpha; if (iRemainingAlpha % (int)dwMaxConsecutiveAlpha) ++iMinGroups; // Determine the minimum number of non-alpha characters we'll need. int iMinNonAlpha = iMinGroups - 1; // Determine the characters remaining. int iRemaining = (int)dwNewLength - iNewChar; // Is there room for a non-alpha char here? if (iRemaining <= (iRemainingAlpha + iMinNonAlpha)) // no. bMustBeAlpha = TRUE; } // Determine the type range. int iMinType = 0; int iMaxType = iTypes - 1; // If next char must be alpha, then alpha chars remain. // Type position 0 contains either UC or LC. // Type position 1 contains LC, non-alpha, or nothing. if (bMustBeAlpha) { if ((iType[1] == eLC) && (iTypes > 1)) iMaxType = 1; else iMaxType = 0; } // If next char may not be alpha, there may be no alpha left to generate. // If so, type position 0 is non-alpha. // O.w., type positions 0 and 1 may both be alpha. else if (bMustNotBeAlpha) { if (iRemainingAlpha) { if (iType[1] >= eDigit) iMinType = 1; else iMinType = 2; } } // Get the type to generate. int iTypePosition; int iTypeToGenerate; const TCHAR *pszSourceString; if (iMinType == iMaxType) // There's only one type. Use it. iTypePosition = iMinType; else { // This algorithm distributes the chances for various types. // If there are 13 LCs to place and one special, there's a // 13/14 chance of placing an LC and a 1/14 chance of placing a // special, due to this algorithm. int iNextTypePosition = 0; for (int i = iMinType; i <= iMaxType; ++i) { for (int j = 0; j < (int)dwToPlace[i]; ++j) { iTypeList[iNextTypePosition++] = i; } } iTypePosition = iTypeList[bRandomType[iNewChar] % iNextTypePosition]; } iTypeToGenerate = iType[iTypePosition]; pszSourceString = szSourceString[iTypeToGenerate]; // Generate the next character. pszNewPassword[iNewChar] = pszSourceString[bRandomChar[iNewChar] % UStrLen(pszSourceString)]; // Keep track of those alphas. if (iTypeToGenerate < eDigit) { ++dwConsecAlpha; --iRemainingAlpha; } else dwConsecAlpha = 0; // Update the types to generate. if (!--dwToPlace[iTypePosition]) { for (int iNextTypePosition = iTypePosition + 1; iNextTypePosition < iTypes; ++iNextTypePosition) { dwToPlace[iNextTypePosition - 1] = dwToPlace[iNextTypePosition]; iType[iNextTypePosition - 1] = iType[iNextTypePosition]; } --iTypes; } } pszNewPassword[dwNewLength] = '\0'; UStrCpy( newPassword, pszNewPassword ); return ERROR_SUCCESS; } /* PasswordGenerate() */ ///////////////////////////////////////////////////////////////////////////////////////////////////////// // Return a random number in the range [iMin..iMax]. // Tries to be fair by discarding values of rand() that give an advantage // to low results. int iRand(int iMin, int iMax) { int iSize = iMax - iMin + 1; int iMaxRand = (int)((((long)RAND_MAX + 1L) / (long)iSize) * (long)iSize); int i; if (iMaxRand > 0) do { i = rand(); } while (i > iMaxRand); else i = rand(); return (i % iSize) + iMin; } // GenerateRandom // // Fills buffers with cryptographically random bytes. void __stdcall GenerateRandom(DWORD dwCount, BYTE* pbRandomType, BYTE* pbRandomChar) { bool bGenerated = false; HCRYPTPROV hProv = NULL; if (CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { if (CryptGenRandom(hProv, dwCount, pbRandomType) && CryptGenRandom(hProv, dwCount, pbRandomChar)) { bGenerated = true; } CryptReleaseContext(hProv, 0); } // if cryptographic generation fails, fallback to random number generator if (!bGenerated) { for (DWORD dwIndex = 0; dwIndex < dwCount; dwIndex++) { pbRandomType[dwIndex] = (BYTE)iRand(0, 255); pbRandomChar[dwIndex] = (BYTE)iRand(0, 255); } } }