Билл Стюарт (bill.stewart@frenchmortuary.com) – системный и сетевой администратор компании French Mortuary, Нью-Мехико

В мае 2009 г. я подготовил HTML-приложение (HTA), RandPass.hta, для удобного формирования случайных паролей с помощью программного кода JScript (подробнее об этом рассказано в статье «Random Passwords on Demand», http://www.windowsitpro.com/article/jscript/random-passwords-on-demand). В приложении имеется четыре флажка, указывающие, какие классы символов (верхнего и нижнего регистров, числа, символы) могут быть использованы в паролях.

Ключевые слова — «могут быть использованы»: в приложении нет принудительного правила для включения выбранных классов символов в случайный пароль. Выбранные флажки просто увеличивают список возможных символов. Например, если установлены флажки верхнего и нижнего регистров, то могут быть получены случайные пароли, содержащие только прописные или только строчные буквы. Такой подход логичен, но несколько противоречит интуитивному восприятию («я выбрал верхний регистр, но в пароле нет ни одной прописной буквы»). Несмотря на этот небольшой изъян, я регулярно применял RandPass.hta и получал много благодарных отзывов от пользователей программы.

Тем не менее, полезно было бы генерировать случайные строки не просто с использованием RandPass.hta, но из командной строки Windows PowerShell. Кроме того, я хотел усовершенствовать алгоритм RandPass.hta: вместо того, чтобы указывать классы символов, которые могут содержаться в строке пароля, лучше указать классы, которые войдут в строку. Для этого я подготовил сценарий Get-RandomString.ps1.

Лучшее — враг хорошего

Синтаксис командной строки для RandomString.ps1:

Get-RandomString.ps1 [[-Length] ] [-LowerCase] [-UpperCase] [-Numbers] [-Symbols] [[-Count] ]

Параметр -Length указывает длину; например,

-Length 32

означает, что Get-RandomString.ps1 будет формировать строки длиной 32 символа. Если не указать этот параметр, значение по умолчанию равно 8. Можно пропустить имя параметра (-Length), если аргумент параметра следует непосредственно после имени сценария в командной строке.

-LowerCase, -UpperCase, -Numbers и -Symbols — переключатели. Все они указывают классы символов, входящих в случайные строки. По умолчанию действует -LowerCase, поэтому если нужно создать случайные строки, не содержащие символов в нижнем регистре, необходимо указать

-LowerCase:$False

Все эти классы символов основываются на символах ASCII и не зависят от языковых настроек. Параметр -Count указывает число формируемых случайных строк (1 по умолчанию). Например,

-Count 10

выдает 10 случайных строк.

Чтобы получить дополнительные сведения о параметрах сценария и образцы команд, введите команду

Get-Help Get-RandomString -Detailed

или

Get-Help Get-RandomString -Full

Принцип действия

Выполнение сценария начинается с объявления инструкции param, которая задает параметры сценария. При этом всем параметрам присваиваются значения по умолчанию. Затем сценарий определяет аргумент параметра -Length. Если он меньше 4, выдается сообщение об ошибке. Длина строки должна быть не менее 4 символов, поскольку таково количество допустимых классов символов. В последнюю очередь нужно убедиться, что в строку входит, по крайней мере, один из классов символов (то есть -LowerCase, -UpperCase, -Numbers или -Symbols). Если не указан ни один из классов символов, выдается сообщение об ошибке.

Затем сценарий объявляет набор значений битовой маски, представляющей четыре класса символов. Дополнительные сведения о применении битовых карт и масок приведены во врезке «Битовые карты как массивы». Затем сценарий формирует четыре массива, содержащих значения символов для каждого из четырех классов символов на основе значений ASCII. Например, массив $charsLower содержит символы ASCII нижнего регистра (то есть каждый элемент массива представляет собой символ ASCII нижнего регистра); массив $charsNumber содержит символы ASCII для цифр. Среди символов, выбранных для массива $charsSymbol, нет ни одного, который мог бы привести к нарушениям работы командной строки (например, одинарных и двойных кавычек, вертикальной черты и символов перенаправления).

После этого сценарий строит массив, сохраняемый в переменной $charList, содержащий все символы классов, указанных в командной строке. Например, если в командной строке присутствуют -LowerCase и -Numbers, то переменная $charList будет содержать символы из массивов $charsLower и $charsNumber. Сценарий также определяет переменную $charSets, которая представляет собой значение битовой карты, где хранятся выбранные наборы символов.

Затем определяется функция test-stringcontents, показанная в листинге 1. В ней используется два параметра: строка и массив символов. Функция возвращает $True, если строка содержит по крайней мере один символ из массива; в противном случае функция возвращает $False. Эта функция используется впоследствии в сценарии, чтобы определить, содержатся ли в случайной строке символы из указанного класса символов.

После того, как определена функция test-stringcontents, сценарий входит в цикл, повторяющийся столько раз, сколько запрошено в параметре -Count. Затем инициируется цикл do, формирующий случайную строку и проверяющий, содержатся ли в строке все запрошенные классы символов. В этом цикле создается переменная $flags (второе значение битовой карты) для отслеживания классов символов. Затем с помощью команды Get-Random случайно выбранный символ из массива $charList присоединяется столько раз, сколько нужно в соответствии с параметром -Length, как показано в листинге 2.

После выполнения программного кода в листинге 2 случайная строка содержит символы, произвольно выбранные из массива $charList. Но это не значит, что строка наверняка содержит определенные символы из каждого выбранного класса символов. Чтобы убедиться, что в строке есть хотя бы один символ из каждого класса, используется функция test-stringcontents (листинг 1). Если строка содержит хотя бы один символ класса, то в битовой карте $flags устанавливается соответствующий бит для этого класса. Если битовая карта не совпадает с битовой картой $charSets, значит, в случайно сформированной строке нет символов всех запрошенных классов, и цикл do повторяется. В противном случае сценарий выдает строку.

Удобный способ генерации случайного пароля

В сценарии Get-RandomString.ps1 (листинг 3) для создания случайных строк используется команда Get-Random и функция обработки массивов и строк. С помощью сценария можно создавать случайные пароли, PIN-коды и многие другие объекты.

Битовые карты как массивы

 

Битовые карты как массивы

Битовая карта представляет собой число, которое программист рассматривает как массив двоичных значений (то есть каждый бит в числе представляет двоичное значение). Проверять и изменять биты в битовой карте можно с помощью маски, представляющей одну или несколько битовых позиций в битовой карте. В сценарии Get-RandomString.ps1 используется два числа в качестве битовых карт. В первой битовой карте, $charSets, хранятся классы символов (то есть нижнего регистра, верхнего регистра, чисел и символов), указанные пользователем в командной строке. Вторая битовая карта, $flags, содержит классы символов, которые действительно присутствуют в случайно сформированной строке. Если битовые карты не совпадают, сценарий генерирует другую случайную строку. Проверить и изменить биты в битовой карте можно с помощью формул в таблице A.

Листинг 1. Функция test-stringcontents

function test-stringcontents([String] $test, [Char[]] $chars) {
foreach ($char in $test.ToCharArray()) {
if ($chars -ccontains $char) { return $TRUE }
}
return $FALSE
}

Листинг 2. Программный код для создания случайной строки

$output = «"
1..$Length | foreach-object {
$output += $charList[(get-random -maximum $charList.Length)]
}

Листинг 3. Сценарий Get-RandomString.ps1

# Get-RandomString.ps1
# Written by Bill Stewart (bstewart@iname.com)
#requires -version 2
<#
. SYNOPSIS
Outputs random strings.
. DESCRIPTION
Outputs one or more random strings containing specified types of characters.
. PARAMETER Length
Specifies the length of the output string(s). The default value is 8. You cannot specify a value less than 4.
. PARAMETER LowerCase
Specifies that the string must contain lowercase ASCII characters (default). Specify -LowerCase:$false if you do not want the random string(s) to contain lowercase ASCII characters.
. PARAMETER UpperCase
Specifies that the string must contain upercase ASCII characters.
. PARAMETER Numbers
Specifies that the string must contain number characters (0 through 9).
. PARAMETER Symbols
Specifies that the string must contain typewriter symbol characters.
. PARAMETER Count
Specifies the number of random strings to output.
. EXAMPLE
PS C:\> Get-RandomString
Outputs a string containing 8 random lowercase ASCII characters.
. EXAMPLE
PS C:\> Get-RandomString -Length 14 -Count 5
Outputs 5 random strings containing 14 lowercase ASCII characters each.
. EXAMPLE
PS C:\> Get-RandomString -UpperCase -LowerCase -Numbers -Count 10
Outputs 10 random 8-character strings containing uppercase, lowercase, and numbers.
. EXAMPLE
PS C:\> Get-RandomString -Length 32 -LowerCase:$false -Numbers -Symbols -Count 20
Outputs 20 random 32-character strings containing numbers and typewriter symbols.
. EXAMPLE
PS C:\> Get-RandomString -Length 4 -LowerCase:$false -Numbers -Count 15
Outputs 15 random 4-character strings containing only numbers.
#>
param(
[UInt32] $Length=8,
[Switch] $LowerCase=$TRUE,
[Switch] $UpperCase=$FALSE,
[Switch] $Numbers=$FALSE,
[Switch] $Symbols=$FALSE,
[Uint32] $Count=1
)
if ($Length -lt 4) {
throw "-Length must specify a value greater than 3»
}
if (-not ($LowerCase -or $UpperCase -or $Numbers -or $Symbols)) {
throw «You must specify one of: -LowerCase -UpperCase -Numbers -Symbols»
}
# Specifies bitmap values for character sets selected.
$CHARSET_LOWER = 1
$CHARSET_UPPER = 2
$CHARSET_NUMBER = 4
$CHARSET_SYMBOL = 8
# Creates character arrays for the different character classes,
# based on ASCII character values.
$charsLower = 97..122 | foreach-object { [Char] $_ }
$charsUpper = 65..90 | foreach-object { [Char] $_ }
$charsNumber = 48..57 | foreach-object { [Char] $_ }
$charsSymbol = 35,36,42,43,44,45,46,47,58,59,61,63,64,
91,92,93,95,123,125,126 | foreach-object { [Char] $_ }
# Contains the array of characters to use.
$charList = @()
# Contains bitmap of the character sets selected.
$charSets = 0
if ($LowerCase) {
$charList += $charsLower
$charSets = $charSets -bor $CHARSET_LOWER
}
if ($UpperCase) {
$charList += $charsUpper
$charSets = $charSets -bor $CHARSET_UPPER
}
if ($Numbers) {
$charList += $charsNumber
$charSets = $charSets -bor $CHARSET_NUMBER
}
if ($Symbols) {
$charList += $charsSymbol
$charSets = $charSets -bor $CHARSET_SYMBOL
}
# Returns True if the string contains at least one character
# from the array, or False otherwise.
function test-stringcontents([String] $test, [Char[]] $chars) {
foreach ($char in $test.ToCharArray()) {
if ($chars -ccontains $char) { return $TRUE }
}
return $FALSE
}
1..$Count | foreach-object {
# Loops until the string contains at least
# one character from each character class.
do {
# No character classes matched yet.
$flags = 0
$output = ""
# Create output string containing random characters.
1..$Length | foreach-object {
$output += $charList[(get-random -maximum $charList.Length)]
}
# Check if character classes match.
if ($LowerCase) {
if (test-stringcontents $output $charsLower) {
$flags = $flags -bor $CHARSET_LOWER
}
}
if ($UpperCase) {
if (test-stringcontents $output $charsUpper) {
$flags = $flags -bor $CHARSET_UPPER
}
}
if ($Numbers) {
if (test-stringcontents $output $charsNumber) {
$flags = $flags -bor $CHARSET_NUMBER
}
}
if ($Symbols) {
if (test-stringcontents $output $charsSymbol) {
$flags = $flags -bor $CHARSET_SYMBOL
}
}
}
until ($flags -eq $charSets)
# Output the string.
$output
}