338 lines
12 KiB
C++
338 lines
12 KiB
C++
//#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 <windows.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
#include <WinCrypt.h>
|
|
|
|
#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);
|
|
}
|
|
}
|
|
}
|