1

Тема: AHK: Генератор паролей, ревизия кода.

Приветствую завсегдатаев Серого Форума! Ради освоения AHK, написал генератор паролей. В целом, он является дальнейшим развитием генератора из этой темы. Прошу, кому не трудно, провести ревизию кода. Для выявления явных или не очень ошибок.


; Версия Autohotkey        : 1.1.22.03 Unicode 32bit
; Автор                    : Stremin
; Последняя модификация    : 16 августа 2015
; Скрипт основан на        : GenPass 0.01, forum.script-coding_com/viewtopic.php?id=7586
;                        :    автор: Irbis
; Использованы ф-ии        : PBKDF2, ahkscript_org/boards/viewtopic.php?t=3477
;                        :    автор: Antonio Bueno
;                        : CreateDIB, ahkscript_org/boards/viewtopic.php?t=3203
;                        :    автор: SKAN
; Описание                :
;    1Pass4All – генератор и анализатор надёжности паролей.
;    Генератор позволяет создавать сложные пароли, без необходимости запоминать их и хра-нить
; где-либо.
;    Для анализа надёжности паролей, используется слегка изменённый алгоритм Джеффа Тоднема /
; Jeff Todnem, реализованный на сайте www.passwordmeter_com. Отличия заключаются в следующем
; (соответственно, для паролей содержащих перечисленные отличия, результат теста будет отличаться):
;    -    Обнаруживаются последовательности xyz / zyx.
;    -    Учитывается символ подчёркивания `_`.
;
;    Пароль создаётся на основе двух строк, указанных пользователем:
;    -    Мастер-пароля – общего для всех программ / сайтов.
;    -    Строки-идентификатора – индивидуальной для каждой программы / сайта.
;
;    и в соответствии с заданными, пользователем, правилами:
;    -    `Длина генерируемого пароля` от 8 до 64 символов.
;    -    `Количество циклов шифрования` от 1 до 9999. Для шифрования используется алгоритм AES256.
;        В интерфейсе нет возможности выбора другого метода шифрования, но в коде скрипта его можно
;        изменить (см. ниже по коду комментарии в функции `passgen`). 
;    -    `Максимум символов подряд из одного набора`, от 0 до 4. Если указано 0, один и тот же набор
;        может быть использован подряд неограниченное количество раз.
;    -    `Использовать каждый символ не больше` от 0 до 4. Если указано 0, один и тот же сим-вол
;        может быть использован неограниченное количество раз. 
;    -    Наборы символов, доступно 6 штук.
;
;    Замечания:
;    -    Настройки с которыми стартует скрипт, можно изменить по своему вкусу. Сделать это можно
;        в тексте скрипта, в секции `Настройки генератора по умолчанию`.
;    -    Если не выбран ни один набор, все настройки группы `Правила` будут неактивны.
;    -    Если выбран один любой набор, неактивной станет настройка: `Максимум символов под-ряд из
;        одного набора`.
;    -    Если выбрано два и более набора, активны все настройки группы `Правила`.
;    -    В случае если ограничение `Использовать каждый символ не больше` действует, может возникнуть
;        ситуация, когда в одном или нескольких алфавитах закончатся символы.
;        Соответственно длина пароля может быть меньше, чем ожидалось. 
;
;    Пример 1:
;        Выбранные наборы    `_`
;        `Длина генерируемого пароля`    8 символов
;        `Максимум символов подряд из одного набора`    неактивно, т.к. выбран один набор
;        `Использовать каждый символ не больше`    1 раза
;
;        Создан пароль длиной 1 символ    `_`
;
;    Пример 2
;        Выбранные наборы    `A-Z`, `_`
;        `Длина генерируемого пароля`    8 символов
;        `Максимум символов подряд из одного набора`    1 (т.е. наборы будут чередоваться)
;        `Использовать каждый символ не больше`    1 раза
;
;        Создан пароль длиной 3 (или 2) символа    `A_B` (или `_A`)
;        Так как наборы чередуются, то после 3 (2) символа, генератор должен переключиться на набор
;        `_`, символы которого уже были использованы указанное кол-во раз.
;
;    Пример 3
;        Выбранные наборы    `A-Z`, `0-9`, `_`
;        `Длина генерируемого пароля`     32 символа
;        `Максимум символов подряд из одного набора`    1 (т.е. наборы будут чередоваться)
;        `Использовать каждый символ не больше`    1 раза
;
;        Создан пароль длиной 22 символа    ` A_0B1C2D3E4F5G6H7I8J9K`
;        После того как символы набора `_` закончились, осталось ещё два набора. Далее аналогично
;        2ому примеру. Генератор переключается между наборами до тех пор, пока в них есть неисполь-
;        зованные символы. С символом `9`, в наборе `0-9` заканчиваются все симво-лы, генератор
;        переключается на набор `A-Z`. После чего должен переключиться на набор `0-9`, но там пусто.
;
;    Исходя из примеров, если длина пароля не соответствует требованиям, нужно выбрать ещё один набор,
; либо увеличить значение параметра `Использовать каждый символ не больше` (либо снять ограничение).

#SingleInstance
#NoEnv

; >>> Настройки генератора по умолчанию менять здесь -----------------------------------------------
; !!! ВНИМАНИЕ: Если не указано, корректность вводимых значений не проверяется !!!

; Скрывать пароль или нет.
;    допустимое значение: true (скрыть) / false (показать)
def_HidePass := true

; Длина генерируемого пароля.
;    допустимое значение (проверяется через UpDown): целое число от 8 до 64.
def_LenPass := 8

; Количество циклов шифрования.
;    допустимое значение (проверяется через UpDown): целое число от 1 до 9999.
def_Iteration := 1000

; Сколько раз подряд, будет использоваться один и тот же набор символов.
;    допустимое значение (проверяется через UpDown): целое число от 0 до 4 (0 неограниченно).
def_LimitSet := 1

; Сколько раз в пароле будет использован один и тот же символ.
;    допустимое значение (проверяется через UpDown): целое число от 0 до 4 (0 неограниченно).
def_LimitChar := 1

; Чекбоксы, активирующие тот или иной набор символов.
;    допустимое значение: true (включение набора) / false (отключение набора).
def_UpCase := true                ; A - Z
def_LowCase := false            ; a - z
def_Num := true                    ; 0 - 9
def_ULine := true                ; символ подчёркивания _
def_SpecSymbols := false        ; спецсимволы ! @ # $ % ^ & * - +
def_Brackets := false            ; скобки ( ) [ ] { } < >

; <<< Настройки генератора по умолчанию ------------------------------------------------------------

; >>> Настройки скрипта ----------------------------------------------------------------------------
script_name := "1Pass4All v0.02-6"
; Дефолтные ширина 454 и высота 146, главного окна. На основе этих значений расчитываются размеры
;    контролов `Вкладки` (высота и ширина), `Мастер-пароль` (ширина), `Строка-Идентификатор` (ширина),
;    `Новый пароль` (ширина), текста во вкладках `Помощь` и `О скрипте`.
m_width := 454, m_heigth := 146

; Для функции PBKDF2.
; Available hash algorithms and their length in bytes
aHashAlgid := {MD5: 0x8003, SHA1: 0x8004, SHA256: 0x800C, SHA384: 0x800D, SHA512: 0x800E}
aHashLength := {MD5: 16, SHA1: 20, SHA256: 32, SHA384: 48, SHA512: 64}

; <<< Настройки скрипта ----------------------------------------------------------------------------

; >>> GUI ------------------------------------------------------------------------------------------
; Всегда поверх всех окон.
Gui, +AlwaysOnTop hwndMainWin
; Шрифт для всего интерфейса.
Gui, Font, s8 w400, Tahoma

; --- GUI - Создание контрола Tab ------------------------------------------------------------------
tmp_w := m_width - 2, tmp_h := m_heigth - 3
Gui, Add, Tab2, x1 y3 w%tmp_w% h%tmp_h% vTabName gCalculatePass, Генератор||Настройки|О скрипте

; --- GUI - Вкладка "Генератор" --------------------------------------------------------------------
Gui, Tab, Генератор
Gui, Margin, 2, 4

tmp_w := m_width - 12
Gui, Add, Edit, x6 y28 w%tmp_w% h20 Section +Center Limit vMasterPass gCalculatePass hwndMP
SetEditCueBanner(MP, "Мастер-пароль")

Gui, Add, Edit, wp hp +Center Limit vStringID gCalculatePass hwndSID
SetEditCueBanner(SID, "Строка-идентификатор")

Gui, Add, Edit, wp hp +Center +ReadOnly vNewPass hwndNP
; Если длина конечного пароля скрывается, переменная `PHstate` не нужна.
PHstate := PlaceholderPass(NP)

; --- Прогресс-бар (ПБ) отображающий надёжность пароля.

; Стиль ПБ (Красный - Жёлтый - Зелёный).
PB_style=
    (LTrim Join|
        ff007f|ffff00|80ff00
    )

; Рамка ПБ. Высота ПБ подобрана под 7 кегль гарнитуры Tahoma.
Gui, Add, Text, wp h11 0x8 +BackgroundTrans E0x20
; Контрол содержащий изображение ПБ.
Gui, Add, Picture, xp+1 yp+1 wp-2 hp-2 0x4E hwndPB_pic
; Информация о контроле Picture (координаты и размеры).
;    - последний символ имени переменной говорит о том какую информацию нужно получить: x, y, w, h.
GuiControlGet, info_, Pos, %PB_pic%
; ? hBM - объект содержащий изображение ПБ. 3x1 размерность стиля ПБ.
hBM := CreateDIB(PB_style, 3, 1, info_w, info_h)
; Указание видимой части контрола Picture, расчитывается как процент от его ширины.
WinSet Region, 0-0 h9 w0, ahk_id %PB_pic%
; Текст с процентом надёжности пароля.
Gui, Font, s7 w1000
Gui, Add, Text, yp-1 wp h10 +Center +BackgroundTrans E0x20 vTxtPerc, % "0%"
Gui, Font, s8 w400

; Вывод изображения ПБ.
DllCall("SendMessage", UInt, PB_pic, UInt, 0x172, UInt, 0, UInt, hBM)

; --- Чекбокс `скрыть пароли`, кнопки копирования в буфер и завершения скрипта.
Gui, Add, CheckBox, xs+4 y+4 w100 h27 Checked%def_HidePass% Section vTogglePass gHideShowPass, Скрыть пароли
Gui, Add, Button, ys w234 hp gCopy2Buffer, Скопировать в буфер обмена
Gui, Add, Button, ys w100 hp vCloseApp gGuiClose, Выход

; --- GUI - Вкладка "Настройки" --------------------------------------------------------------------
Gui, Tab, Настройки
Gui, Margin, 2, 2

; --- Группа GUI настроек - 'Правила'.
Gui, Add, GroupBox, x5 y28 w310 h113, % " Правила"

Gui, Add, Text, xp+9 yp+21 w240 h20 Section, Длина генерируемого пароля
; Предотвращение ввода любых символов кроме цифр - `+Number`.
Gui, Add, Edit, x+0 yp-2 w52 h18 +Center +Number vToggleLen
; Значения забираются из `UpDown`, для предотвращение выхода значений за границы диапазона.
Gui, Add, UpDown, Range8-64 vLenPass, %def_LenPass%

Gui, Add, Text, xs w240 h20, Количество циклов шифрования
Gui, Add, Edit, x+0 yp-2 w52 h18 +Center +Number vToggleIter
Gui, Add, UpDown, Range1-9999 vIteration, %def_Iteration%

; Если указано 0, один и тот же набор будет использоваться подряд, неограниченное кол-во раз.
Gui, Add, Text, xs w240 h20, Максимум символов подряд, из одного набора
Gui, Add, Edit, x+0 yp-2 w52 h18 +Center +Number vToggleSet
Gui, Add, UpDown, Range0-4 vLimitSet, %def_LimitSet%

; Если указано 0, каждый символ допустимо использовать неограниченное кол-во раз.
Gui, Add, Text, xs w240 h20, Использовать каждый символ не больше
Gui, Add, Edit, x+0 yp-2 w52 h18 +Center +Number vToggleChar
Gui, Add, UpDown, Range0-4 vLimitChar, %def_LimitChar%

; --- Группа GUI настроек - 'Наборы символов'.
Gui, Add, GroupBox, x317 y28 w132 h113, % " Наборы символов"

Gui, Add, CheckBox, xp+9 yp+18 w58 h20 Section Checked%def_UpCase% vUpCase gLockGUI
    , A...Z
Gui, Add, CheckBox, wp hp Checked%def_LowCase% vLowCase gLockGUI
    , a...z

Gui, Add, CheckBox, ys wp hp Checked%def_Num% vNum gLockGUI
    , 0...9
Gui, Add, CheckBox, wp hp Checked%def_ULine% vULine gLockGUI
    , _

Gui, Add, CheckBox, xs w120 hp Checked%def_SpecSymbols% vSpecSymbols gLockGUI
    , % "! @ # $ % ^ && * - +"
Gui, Add, CheckBox, wp hp Checked%def_Brackets% vBrackets gLockGUI
    , % "( ) [ ] { } < >"

; --- GUI - Вкладка "О скрипте" --------------------------------------------------------------------
Gui, Tab, О скрипте

tmp_h -= 31
Gui, Add, Text, x6 y28 w%tmp_w% h%tmp_h%
        , % "1Pass4All – генератор и анализатор надёжности паролей. Позволяет создавать слож-`n"
        . "ные пароли, без необходимости запоминать их и хранить где-либо.`n`n"
        . "Параметры группы 'Правила', сверху вниз (подробнее см.описание в тексте скрипта):`n"
        . "   - 1ый от 8 до 64.`n"
        . "   - 2ой от 0 до 4 (0 - неограниченно).`n"
        . "   - 3ий от 1 до 9999.`n"
        . "   - 4ый от 0 до 4 (0 - неограниченно)."

; --- GUI - Вывод интерфейса на экран --------------------------------------------------------------
Gui, Show, Center w%m_width% h%m_heigth%, %script_name%
; Установка фокуса на кнопку `Выход` сделан для того, чтобы при запуске скрипта, в полях для ввода
;     мастер-пароля и строки-идентификатора были видны поясняющие надписи.
GuiControl, Focus, Выход

; см. описание к метке LockGUI. Здесь необходимо для деактивации элементов GUI, в зависимости от 
;    дефолтных настроек, сделанных пользователем.
goto LockGUI

Return

; --- GUI - Завершение работы скрипта --------------------------------------------------------------
GuiEscape:    ; предопределённая метка, переход к которой происходит при нажатии клавиши ESC.
GuiClose:    ; предопределённая метка, переход к которой происходит при закрытии окна скрипта.
ExitApp

; <<< GUI ------------------------------------------------------------------------------------------

Copy2Buffer:

    if(calcpass != "")
        Clipboard := calcpass

    return

; --- Активация / Деактивация правил генератора в зависимости от количества наборов символов -------
; 0 - отключены все правила (пароль не генерируется)
; 1 - отключено ограничение на использование подряд одного и того же набора символов.
; 2 и более - все правила активны
LockGUI:
    Gui, Submit, NoHide
    
    ; Перечисление наборов, которые будут использованы при создании пароля.
    CheckedSet := [UpCase, LowCase, Num, ULine, SpecSymbols, Brackets]
    
    ; --- Подсчёт количества отмеченных наборов.
    countSet := 0
    
    while(countSet < 2 and A_Index <= checkedSet.length())
        if(checkedSet[A_Index])
            countSet++

    ; --- Активация / Деактивация правил генератора.
    listControl := ["ToggleLen", "ToggleIter", "ToggleSet", "ToggleChar"]
    
    if(countSet = 0)
        toggle := [0, 0, 0, 0]
    else if (countSet = 1)
        toggle := [1, 1, 0, 1]
    else
        toggle := [1, 1, 1, 1]
    
    loop % listControl.length()
        GuiControl, % "Enable" . toggle[A_Index], % listControl[A_Index]
    
    toggle := ""

    ; Необходимо для обновления состояния отображения, контролов `UpDown` на вкладке `Настройки`, при
    ;    активации / деактивации группы настроек `Правила`.
    WinSet, Redraw, , ahk_id %MainWin%
    return

; --- Скрытие / Показ мастер-пароля и нового пароля ------------------------------------------------
HideShowPass:
    Gui, Submit, NoHide
    
    hide_pass(MP, TogglePass)
    
    ; Если нужно скрывать длину конечного пароля.
    if(calcpass != "")
        if(TogglePass)
            {
            hide_pass(NP, 1)
            PlaceholderPass(NP, "БОЛЬШОЙСЕКРЕТ")
            }
        else
            {
            hide_pass(NP, 0)
            PlaceholderPass(NP, calcpass)
            }
    else
        {
        hide_pass(NP, 0)
        PlaceholderPass(NP)
        }
    
    ; Если не нужно скрывать длину конечного пароля.
    /*
    if(!PHstate)
        hide_pass(NP, TogglePass)
    else
        hide_pass(NP, 0)
    */    
    return

; --- Вычисление / Анализ пароля -------------------------------------------------------------------
CalculatePass:
    Critical On
    Gui Submit, NoHide
    
    ; Если нужно скрывать длину пароля.
    if(TabName = "Генератор" and MasterPass != "" and StringID != "")
        {
        calcpass := passgen(MasterPass, StringID, Iteration, LenPass, CheckedSet, LimitSet, LimitChar)
        pass_score := pwdmeter2(calcpass)
        
        GuiControl, , TxtPerc, % pass_score . "%"
        WinSet Region, % "0-0 h9 w" . info_w * pass_score // 100, ahk_id %PB_pic%
        }
    else
        {
        calcpass := ""
        
        GuiControl, , TxtPerc, % "0%"
        WinSet Region, 0-0 h9 w0, ahk_id %PB_pic%
        }
    ; Если не нужно скрывать длину пароля.
    /*
    if(MasterPass != "" and StringID != "")
        {
        calcpass := passgen(MasterPass, StringID, Iteration, LenPass, CheckedSet, LimitSet, LimitChar)
        PHstate := PlaceholderPass(NP, calcpass)
        }
    else
        PHstate := PlaceholderPass(NP)
    */
    
    goto HideShowPass
    
    return

; >>> Функции --------------------------------------------------------------------------------------

; --- Функции - Свои -------------------------------------------------------------------------------
PlaceholderPass(hID, str = "")
    ; Текст подсказки и вывод готового пароля.
    {    
    if(str = "")
        {
        Gui, Font, c0xA0A0A0
        GuiControl, Font, %hID%
        GuiControl, , %hID%, Ваш новый пароль
        Gui, Font, cBlack
        
        return true
        }
    else
        {
        Gui, Font, cBlack
        GuiControl, Font, %hID%
        GuiControl, , %hID%, %str%
        
        return false
        }
    }

hide_pass(hID, state)
    ; Скрытие / Показ пароля.
    {
    ; `9679` юникод символа `•` в гарнитуре Tahoma.
    GuiControl, % state ? "+Password" . CHR(9679) : "-Password", % hID
    
    WinSet, Redraw, , ahk_id %hID%
    }

passgen(master, str_id, iter, len_p, sel_set, lim_set, lim_char)
    ; Функция, конвертирующая строку, состоящую из символов 16-ичной системы счисления - в строку,
    ;    состоящую из символов, входящих в указанные наборы.
    ;
    ;    master - мастер-пароль.
    ;    str_id - строка идентификатор.
    ;    iter - кол-во циклов шифрования.
    ;    len_p - длина пароля.
    ;    sel_set - выбранные наборы символов для создания пароля.
    ;    lim_set - сколько раз подряд, допустимо использовать один и тот же набор символов, 0 - неограничено.
    ;    lim_char - сколько раз допустимо использовать один и тот же символ, 0 - неограничено.
    {
    full_set := ["ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                , "abcdefghijklmnopqrstuvwxyz"
                , "1234567890"
                , "_"
                , "!@#$%^&*-+"
                , "()[]{}<>"]
    ; Наборы выбранные для создания пароля.
    use_set := []
    pass_out := ""

    ; --- Создание `рабочего` набора.
    loop % sel_set.length()
        if(sel_set[A_Index])
            ; `id_set` см. ниже, секцию комментариев `--- Символ найден`
            use_set.Push(["id_set" . (use_set.length() + 1) , full_set[A_Index]])
    
    ; Если выбран хотя бы один набор - инициализация начальных значений, в противном случае - выход.
    if(use_set.length())
        {
        ; Информация о символах в пароле, необходима для исключения повторов символов.
        char_in_pass := {}
        ; Остановка обработки пароля, если колво наборов равно 0 или
        ;    если набор остался 1, а для формирования пароля было выбрано 2 и более.
        stop_proc := false
        ; Расчёт `заготовки` пароля (строка - состоит из символов 16-ричной системы счисления).
        ; Допустимые значения алгоритма шифрования : MD5, SHA1, SHA256, SHA384, SHA512.
        pass_in := PBKDF2(master, str_id, iter, len_p * 2, "SHA256")
        
        ; Снятие ограничений с наборов, если указано 0 или кол-во используемых наборов равно 1.
        if(lim_set == 0 or use_set.length() == 1)
            lim_set := len_p
        ; Снятие ограничений с символов, если указано 0
        if(lim_char == 0)
            lim_char := len_p
        }
    else
        stop_proc := true
    
    ;--- Обработка пароля блоками по 4 символа.
    while(A_Index <= len_p and !stop_proc)
        {
        block := SubStr(pass_in, A_Index * 4 - 3, 4)
        ; 1, 2 символы - номер набора.
        cur_set := "0x" . SubStr(block, 1, 2)
        ; 3, 4 символы - номер символа в наборе.
        cur_char := "0x" . SubStr(block, 3, 2)
        char_found := false
        
        while(!char_found and !stop_proc)
            {
            ; Масштабирование номера набора и переход к следующему набору (+1), если текущий набор
            ;    не подходит.
            cur_set := Mod(cur_set, use_set.length()) + 1
            
            if(cur_set != prev_set)
                ; Счётчик символов из одного и того же набора, если последовательность не прервана
                ;    другим набором.
                count_set := 0
            
            if(count_set >= lim_set)
                {
                if(use_set.MaxIndex() <= 1)
                    stop_proc := true
                }
            else
                {
                ; --- Символ найден.
                char_found := true
                prev_set := cur_set
                count_set++
                cur_char := SubStr(use_set[cur_set, 2], Mod(cur_char, StrLen(use_set[cur_set, 2])) + 1, 1)
                pass_out .= cur_char
                
                ; `id_set` + #набора + #символа - необходимо для того, чтобы разтличать символы верхнего
                ;     и нижнего регистра (т.к. ключи ассоциативного массива не чувствительны к регистру).
                if(char_in_pass.HasKey(use_set[cur_set, 1] . cur_char))
                    char_in_pass[use_set[cur_set, 1] . cur_char]++
                else
                    char_in_pass.Insert(use_set[cur_set, 1] . cur_char, 1)
                
                if(char_in_pass[use_set[cur_set, 1] . cur_char] >= lim_char)
                    use_set[cur_set, 2] := StrReplace(use_set[cur_set, 2], cur_char)
                
                if(StrLen(use_set[cur_set, 2]) == 0)
                    use_set.RemoveAt(cur_set)
                
                if(use_set.length() == 0)
                    stop_proc := true
                }
            }
        }

    return pass_out
    }

pwdmeter2(pass)
    ; AHK-реализация измерителя надёжности паролей - www.passwordmeter_com.
    ; версия: 0.02
    ; Отличия от оригинала, в оригинальной версии:
    ;    1. символ подчёркивания `_` не учитывается.
    ;    - т.к. оригинал не учитывает символ подчёркивания.
    ;    - неверно составлено условие проверок - (пароль `состоит только из букв` / `состоит только из цифр`).
    ;    > как следствие для паролей состоящих из `A-Z` и `_` или `0-9` и `_`
    ;        например FN_D_Y, 49_13_7
    ;    может возникнуть ложное предположение о том, что пароль состоит только из букв или цифр.
    ;    2. не обнаруживаются последовательности xyz / zyx, из-за занижения на 1 в условии.
    {
    score := 0
    
    if(StrLen(pass))
        {
        ; --- Инициализация начальных значений -----------------------------------------------------
        ; Можно затолкать в массив, но удобнее использовать переменные со значащими именами.
        test_UpCase := "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        test_LowCase := "abcdefghijklmnopqrstuvwxyz"
        test_Num := "0123456789"
        test_Symbol := "!@#$%^&*()"
        ; Набор, к которому принадлежал предыдущий символ.
        prev_testSet := ""
        
        ; --- Бонусы, процент надёжности растёт.
        ; Полная длина тестируемого пароля.
        f_len_pass := StrLen(pass)
        ; В оригинальной версии из пароля удаляются не только пробелы.
        pass := StrReplace(pass, A_Space)
        ; Длина пароля без пробелов.
        ws_len_pass := StrLen(pass)
        ; Колво символов верхнего регистра A-Z.
        count_UpCase := 0
        ; Колво символов нижнего регистра a-z.
        count_LowCase := 0
        ; Колво цифр.
        count_Num := 0
        ; Колво всех остальных символов.
        count_Symbol := 0
        ; Колво символов + цифр в середине пароля, т.е 1ый и последний символы пароля, в счёт не идут.
        count_MidNS := 0
        ; Какому количеству требований соответствует пароль:
        ;    - минимальная длина 8 символов.
        ;    - содержит любые символы верхнего регистра A-Z.
        ;    - содержит любые символы нижнего регистра a-z.
        ;    - содержит любые цифры.
        ;    - содержит любые символы (не A-Z, a-z, 0-9), кроме ` `.
        count_Req := 0
        
        ; --- Штрафы, процент надёжности падает.
        ; Если в пароле только символы верхнего и нижнего регистра, равно длине пароля.
        coutn_OnlyLet := 0
        ; Если в пароле только цифры, равно длине пароля.
        coutn_OnlyNum := 0
        ; Колво всех одинаковых символов.
        count_RepChar := 0
        ; Разница расстояний между двумя идентичными символами, делённая на длину пароля.
        ;    Суммируется когда встречает пару. 
        ;    ! чем дальше отстоят друг от друга символы, тем меньше штраф.
        RepInc := 0
        ; Колво повторных, последовательных использований:
        ;    - набора с символами верхнего регистра. (пример absdf, колво 4)
        count_ConUpCase := 0
        ;    - набора с символами нижнего регистра.
        count_ConLowCase := 0
        ;    - набора с цифрами.
        count_ConNum := 0
        ; Колво символов A-Z, позиции которых (в алфавите) последовательно возрастают или убывают.
        ;    (пример ABCDF, IHGFE).
        ;    ! не чувствительно к регистру символов.
        ;    ! учитываются последовательности длинной от 3х символов включительно.
        count_SeqLetter := 0
        ; Аналогично для цифр.
        count_SeqNum := 0
        ; Аналогично для символов сопоставленных цифрам 0-9, вверху клавиатуры.
        count_SeqSym := 0
        
        ; --- Сбор информации о пароле - Подсчёт:
        ;    колва символов `count_UpCase`, `count_LowCase`, `count_Num`, `count_Symbol`.
        ;    колва последовательностей `count_ConUpCase`, `count_ConLowCase`, `count_ConNum`, `count_MidNS`.
        ;    повторяющихся символов `count_RepChar`, `RepInc`.
        loop, Parse, pass
            {
            if(InStr(test_UpCase, A_LoopField, true))
                {
                count_UpCase++
                
                if(prev_testSet = "UpCase")
                    count_ConUpCase++
                
                prev_testSet := "UpCase"
                }
            else if(InStr(test_LowCase, A_LoopField))
                {
                count_LowCase++
                
                if(prev_testSet = "LowCase")
                    count_ConLowCase++
                
                prev_testSet := "LowCase"
                }
            else if(InStr(test_Num, A_LoopField))
                {
                count_Num++
                
                if(A_Index > 1 and A_Index < ws_len_pass)
                    count_MidNS++
                
                if(prev_testSet = "Num")
                    count_ConNum++
                
                prev_testSet := "Num"
                }
            else ;if(A_LoopField != "_") ; в оригинале присутствует.
                {
                count_Symbol++
                    
                if(A_Index > 1 and A_Index < ws_len_pass)
                    count_MidNS++
                                
                prev_testSet := "Symbol"
                }
            
            ; --- Сбор информации о пароле - Повторы символов `count_RepChar` и штраф за повторы `RepInc`.
            char_index := A_Index
            char_value := A_LoopField
            CharExists := false
            
            loop, Parse, pass
                if(char_value == A_LoopField and char_index != A_Index)
                    {
                    CharExists := true
                    RepInc += Abs(ws_len_pass / (A_Index - char_index))
                    }
            
            if(CharExists)
                {
                count_RepChar++
                UnqChar := ws_len_pass - count_RepChar
                RepInc := UnqChar ? Ceil(RepInc / UnqChar) : Ceil(RepInc)
                }
            }
        
        ; --- Подсчёт `count_Req` скольким требованиям удовлетворяет пароль.
        if(f_len_pass >= 8)
            count_Req++
        
        if(count_UpCase)
            count_Req++
        
        if(count_LowCase)
            count_Req++
        
        if(count_Num)
            count_Req++
        
        if(count_Symbol)
            count_Req++
        
        ; --- Возрастающие / убывающие последовательности символов.
        loop % StrLen(test_UpCase) - 2
            {
            forward_str := SubStr(test_UpCase, A_Index, 3)
            backward_str := StrReverse(forward_str)
            
            if(InStr(pass, forward_str) or InStr(pass, backward_str))
                count_SeqLetter++
            }

        loop % StrLen(test_Num) - 2
            {
            forward_str := SubStr(test_Num, A_Index, 3)
            backward_str := StrReverse(forward_str)
            
            if(InStr(pass, forward_str) or InStr(pass, backward_str))
                count_SeqNum++
            }
            
        loop % StrLen(test_Symbol) - 2
            {
            forward_str := SubStr(test_Symbol, A_Index, 3)
            backward_str := StrReverse(forward_str)
            
            if(InStr(pass, forward_str) or InStr(pass, backward_str))
                count_SeqSym++
            }
        
        ; --- Общий счёт - Бонусы.
        score := f_len_pass * 4
        
        if(count_UpCase > 0 and count_UpCase < f_len_pass)
            score += (f_len_pass - count_UpCase) * 2
        
        if(count_LowCase > 0 and count_LowCase < f_len_pass)
            score += (f_len_pass - count_LowCase) * 2
        
        if(count_Num > 0 and count_Num < f_len_pass)
            score += count_Num * 4
        
        score += count_Symbol * 6
        score += count_MidNS * 2
        
        if(count_Req > (f_len_pass >= 8 ? 3 : 4))
            score += count_Req * 2
        
        ; --- Общий счёт - Штрафы.
        ; Если пароль состоит только из символов A-Z (регистр не важен).
        count_OnlyLet := 0
        if(count_UpCase + count_LowCase = f_len_pass)
            {
            score -= f_len_pass
            count_OnlyLet := f_len_pass
            }
        
        ; Если пароль состоит только из цифр.
        count_OnlyNum := 0
        if(count_Num = f_len_pass)
            {
            score -= f_len_pass
            count_OnlyNum := f_len_pass
            }
        
        if(count_RepChar)
            score -= RepInc
        
        score -= count_ConUpCase * 2
        score -= count_ConLowCase * 2
        score -= count_ConNum * 2
        score -= count_SeqLetter * 3
        score -= count_SeqNum * 3
        score -= count_SeqSym * 3

        if(score < 0)
            score := 0
        
        if(score > 100)
            score := 100
        }
    
    return score
    }

StrReverse(str)
    ; Реверс строки.
    {    
    loop, Parse, str
        str_rev := A_LoopField . str_rev
    
    return str_rev
    }
; --- Функции - Сторонние --------------------------------------------------------------------------

SetEditCueBanner(hID, Cue)
    ; Текст-подсказка в контроле Edit
    ;    - альтернатива (На ANSI версии не работает.)
    ;        SendMessage, 0x1501, 0, "ТЕКСТ_ПОДСКАЗКИ",, ahk_id %ID_КОНТРОЛА%
    {
    Static EM_SETCUEBANNER := 0x1501
    Return DllCall("User32.dll\SendMessageW", "Ptr", hID, "Uint", EM_SETCUEBANNER, "Ptr", True, "WStr", Cue)
    }

CreateDIB(PixelData, W, H, ResizeW = 0, ResizeH = 0, Gradient = 1)
    ; Создание растровых изображений в контроле Picture.
    ;    - автор: SKAN
    ;    - ahkscript_org/boards/viewtopic.php?t=3203
    {
    WB := Ceil((W * 3) / 2) * 2, VarSetCapacity(BMBITS, WB * H + 1, 0)
    P := &BMBITS
    
    Loop, Parse, PixelData, |
        P := Numput("0x" A_LoopField, P + 0) - (W & 1 && !Mod(A_Index * 3, W * 3) ? 0 : 1)
    
    hBM := DllCall("CreateBitmap", Int, W, Int, H, UInt, 1, UInt, 24, UInt, 0)    
    hBM := DllCall("CopyImage", UInt, hBM, Int, 0, Int, 0, Int, 0, UInt, 0x2008) 
    DllCall("SetBitmapBits", UInt, hBM, UInt, WB * H, UInt, &BMBITS)
    hBM := !Gradient ? DllCall("CopyImage", UInt, hBM, UInt, 0, Int, 0, Int, 0, Int, 8 ) : hBM
    
    Return DllCall("CopyImage", UInt, hBM, Int, 0, Int, ResizeW, Int, ResizeH, Int, 0x200C, UInt)
    }

; AutoHotkey Version: AutoHotkey 1.1
; Language:           English
; Platform:           Win7 SP1
; Author:             Antonio Bueno <user atnbueno at Google's free e-mail service>
; Description:        Implementation of PBKDF2 using MS-CAPI HMACs
; Last Modification:  2014-05-03

PBKDF2(sPassword, sSalt, nIterations = 10000, nLength = 0, sAlgo = "SHA1")
; Calculates PBKDF2 for a (password, salt) pair that can be either an UTF-8-encoded string or an hex
; string. Optional parameters are the number of iterations, the output length and the hash algorithm.
; It depends on the functions below (Wide2UTF8, Hex2Bin, RawPBKDF2, and Bin2Hex)
    {
    global aHashAlgid, aHashLength
    ; Basic error checking
    If (nIterations <= 0)
        Throw "PBKDF2 ERROR: Invalid number of iterations (" nIterations ")"
    If (aHashAlgid[sAlgo] = "")
        Throw "PBKDF2 ERROR: Unknown hash function (" sALgo ")"
    ; Prepares parameters for RawPBKDF2
    If (nLength = 0)
        nLength := aHashLength[sAlgo]
    ; sPassword and sSalt can be either an UTF-8-encoded string or an hex string
    If (!RegExMatch(sPassword, "i)^([0-9A-F][0-9A-F])+$"))
        nPassword := Wide2UTF8(sPassword, Password)
    else
        nPassword := Hex2Bin(sPassword, Password)
    If (!RegExMatch(sSalt, "i)^([0-9A-F][0-9A-F])+$"))
        nSalt := Wide2UTF8(sSalt, Salt)
    else
        nSalt := Hex2Bin(sSalt, Salt)
    ; Calculates PBKDF2
    RawPBKDF2(Password, nPassword, Salt, nSalt, nIterations, Output, nLength, sAlgo)
    ; Outputs result in hexadecimal format
    Return Bin2Hex(Output, nLength)
    }
    
RawPBKDF2(ByRef Password, nPassword, ByRef Salt, nSalt, nIterations, ByRef Output, nOutput, sAlgo)
    {
    global aHashAlgid, aHashLength
    ; MS-CAPI constants used to do HMACs
    CALG_HMAC            := 0x8009
    CALG_RC2             := 0x6602
    CRYPT_IPSEC_HMAC_KEY := 0x0100
    CRYPT_VERIFYCONTEXT  := 0xF0000000
    CUR_BLOB_VERSION     := 0x02
    HP_HASHVAL           := 0x02
    HP_HMAC_INFO         := 0x05
    PLAINTEXTKEYBLOB     := 0x08
    PROV_RSA_FULL        := 0x01
    ; Reserves memory for hashing and XORing and storing the final result
    VarSetCapacity(Hash, Ceil(aHashLength[sAlgo] / 8) * 8, 0)
    VarSetCapacity(XORSum, Ceil(aHashLength[sAlgo] / 8) * 8, 0)
    nBlockCount := Ceil(nOutput / aHashLength[sAlgo])
    VarSetCapacity(Output, aHashLength[sAlgo] * nBlockCount, 0)
    ; If the output is not longer than the specified hash the following loop does a single iteration
    Loop % nBlockCount
        {
        ; The block count must be stored as an INT32_BE (the expression below is OK up to a block count of 255)
        nHash := Hex2Bin(Bin2Hex(Salt, nSalt) "000000" Bin2Hex(Chr(A_Index), 1), Hash)
        VarSetCapacity(HmacInfo, 4 * A_PtrSize, 0)
        NumPut(aHashAlgid[sAlgo], HmacInfo, 0, "UInt") ; Selects hashing algorithm and default padding
        nKeyBlobSize := 12 + nPassword
        VarSetCapacity(keyBlob, nKeyBlobSize, 0)
        NumPut(PLAINTEXTKEYBLOB, keyBlob, 0, "Char")
        NumPut(CUR_BLOB_VERSION, keyBlob, 1, "Char")
        NumPut(CALG_RC2, keyBlob, 4, "UInt")
        NumPut(nPassword, keyBlob, 8, "Char")
        DllCall("RtlMoveMemory", "UInt", &keyBlob + 12, "UInt", &Password, "Int", nPassword) 
        DllCall("advapi32\CryptAcquireContext", "UIntP", hProv, "UInt", 0, "UInt", 0, "UInt", PROV_RSA_FULL, "UInt", CRYPT_VERIFYCONTEXT)
        DllCall("advapi32\CryptImportKey", "UInt", hProv, "UInt", &keyBlob, "UInt", nKeyBlobSize, "UInt", 0, "UInt", CRYPT_IPSEC_HMAC_KEY, "UIntP", hKey)
        ; Iteration zero
        DllCall("advapi32\CryptCreateHash", "UInt", hProv, "UInt", CALG_HMAC, "UInt", hKey, "UInt", 0, "UIntP", hHmacHash)
        DllCall("advapi32\CryptSetHashParam", "UInt", hHmacHash, "UInt", HP_HMAC_INFO, "UInt", &HmacInfo, "UInt", 0)
        DllCall("advapi32\CryptHashData", "UInt", hHmacHash, "UInt", &Hash, "UInt", nHash, "UInt", 0)
        DllCall("advapi32\CryptGetHashParam", "UInt", hHmacHash, "UInt", HP_HASHVAL, "UInt", &XORSum, "UIntP", aHashLength[sAlgo], "UInt", 0)
        DllCall("advapi32\CryptDestroyHash", "UInt", hHmacHash)
        DllCall("RtlMoveMemory", "UInt", &Hash, "UInt", &XORSum, "Int", aHashLength[sAlgo])
        ; End of iteration zero
        Loop % nIterations-1
            {
            DllCall("advapi32\CryptCreateHash", "UInt", hProv, "UInt", CALG_HMAC, "UInt", hKey, "UInt", 0, "UIntP", hHmacHash)
            DllCall("advapi32\CryptSetHashParam", "UInt", hHmacHash, "UInt", HP_HMAC_INFO, "UInt", &HmacInfo, "UInt", 0)
            DllCall("advapi32\CryptHashData", "UInt", hHmacHash, "UInt", &Hash, "UInt", aHashLength[sAlgo], "UInt", 0)
            DllCall("advapi32\CryptGetHashParam", "UInt", hHmacHash, "UInt", HP_HASHVAL, "UInt", &Hash, "UIntP", aHashLength[sAlgo], "UInt", 0)
            DllCall("advapi32\CryptDestroyHash", "UInt", hHmacHash)
            ; XOR is done in 64-bit (8-byte) blocks (smaller blocks make the XORing significantly slower)
            Loop % Ceil(aHashLength[sAlgo] / 8)
                {
                nOffset := (A_Index - 1) * 8
                NumPut(NumGet(XORSum, nOffset, "Int64") ^ NumGet(Hash, nOffset, "Int64"), &XORSum, nOffset, "Int64")
                }
            }
        DllCall("advapi32\CryptDestroyKey", "UInt", hKey)
        DllCall("advapi32\CryptReleaseContext", "UInt", hProv, "UInt", 0)
        DllCall("RtlMoveMemory", "UInt", &Output+(A_Index-1)*aHashLength[sAlgo], "UInt", &XORSum, "Int", aHashLength[sAlgo])
        }
    }
    
Wide2UTF8(sInput, ByRef Output)
    {
    nOutputSize := StrPut(sInput, "UTF-8")
    VarSetCapacity(Output, nOutputSize)
    StrPut(sInput, &Output, nOutputSize, "UTF-8")
    Return nOutputSize-1
    }

Bin2Hex(ByRef Input, nInput)
    {
    sIntegerFormat := A_FormatInteger
    SetFormat Integer, H
    Loop % nInput
        sOutput .= SubStr(*(&Input + A_Index - 1) + 0x100, -1)
    SetFormat Integer, % sIntegerFormat
    Return sOutput
    }
    
Hex2Bin(sInput, ByRef Output)
    {
    VarSetCapacity(Output, StrLen(sInput) // 2)
    Loop % StrLen(sInput) // 2
        NumPut("0x" . SubStr(sInput, 2 * A_Index - 1, 2), Output, A_Index - 1, "Char")
    Return StrLen(sInput) // 2
    }

; <<< Функции --------------------------------------------------------------------------------------

2

Re: AHK: Генератор паролей, ревизия кода.

Генератор позволяет создавать сложные пароли, без необходимости запоминать их и хранить где-либо.

Не знаю зачем были те 2 темы, но зачем в принципе такой пароль, который потом и не нужен?

По вопросам возмездной помощи пишите на E-Mail: serzh82saratov@mail.ru Telegram: https://t.me/sergiol982
Win10x64 AhkSpy, Hotkey, ClockGui

3

Re: AHK: Генератор паролей, ревизия кода.

Хендлы в api-функциях должны иметь размерность Ptr, а не UInt, иначе видно, что всё просто передрано с постов офф. форума времён ahk-basic. Кроме того, необходимость приложения, генерирующего пароли, имхо, стремится к нулю, т. к. для генерации достаточно сложного пароля достаточно просто потыкать по клавиатуре. Куда более полезно приложение для их хранения в зашифрованном виде и с организацией удобного доступа (по присвоенным меткам, с доп. информацией и т. п.).

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

4 (изменено: Stremin, 2015-08-17 10:48:56)

Re: AHK: Генератор паролей, ревизия кода.

serzh82saratov пишет:

...зачем в принципе такой пароль, который потом и не нужен?...

1. Затем, что снова введя те же мастер-пароль и строку-идентификатор, вы снова получите прежний пароль.
2. В приложении PWGen, есть схожий функционал `Инструменты > Генератор мастер паролей`. Стало быть, если реализовано, значит кому-то нужно и кто-то пользуется.
3. www.pwdhash.com - схожая идея, академическая разработка Стенфордского университета.

Замечание к 2 и 3, нет аналогичных возможностей настраивать получаемый пароль.

teadrinker пишет:

...для достаточно сложного пароля, достаточно просто потыкать по клавиатуре...

Вы потыкали по клавиатуре, и получили что-нибудь подобное `!y?G@v%)_POQ(+;%`, вы запомните такую комбинацию?...

В предлагаемом решении вы вводите:

Мастер-пароль - `СЕКРЕТНЫЙ_ПАРОЛЬ`.
Идентификатор - `СЕРЫЙ_ФОРУМ`.
Получаете - `#(h6&AaWnI_]Y}kQ`.

ИМХО, `СЕКРЕТНЫЙ_ПАРОЛЬ` и `СЕРЫЙ_ФОРУМ` запомнить гораздо легче. Впрочем, я согласен, такой вариант подойдёт не во всех ситуациях. Кроме того, касательно полезности, скрипт писался в первую очередь для знакомства с AHK. Полезность приложения стояла на втором плане.

teadrinker пишет:

...Хендлы в api-функциях должны иметь размерность Ptr, а не UInt, иначе видно, что всё просто передрано с постов офф. форума...

Спасибо, за полезное замечание и за то что посмотрели код. Искренне, спасибо.
То есть, если хендлы будут иметь размерность Ptr, не будет видно, что функции позаимствованы с офф.форума?

teadrinker пишет:

...Куда более полезно приложение для их хранения в зашифрованном виде и с организацией удобного доступа...

Действительно, это так, и такие приложения к счастью уже существуют. Но, о них мне стало известно, только, во время  написания генератора. Повторять функционал даже частично не имело смысла, но процесс уже пошёл и бросать его, мне не очень хотелось.

5

Re: AHK: Генератор паролей, ревизия кода.

Ну что же, раз звезды зажигают... (с).
Я, правда, потом отказался от развития программы, в пользу встроенного в браузер управления паролями. Несколько заметок по организации работы с программой, с точки зрения эргономики:

  • Так как мастер-пароль вводится один раз, можно не держать для него отдельное поле ввода, а забивать один раз при запуске скрипта.

  • Сбрасывать и требовать повторного ввода мастер-пароля - по команде/хоткею или по таймауту, если юзер больше нескольких минут Away from computer. (в целях безопасности).

  • Строка-идентификатор, как я понимаю, чаще всего ассоциируется с названием сайта, поэтому лучше, если программа по хоткею будет из названия активной вкладки браузера извлекать строку вида www.***.com и вводить сгенерированный пароль в предназначенное для этого поле. Для остальных программ тоже подойдет. В таком случае не будет потребности вводить что-то каждый раз.

  • Некоторые сайты принудительно просят поменять пароль через определенное время, да и вообще это хорошее правило. Для каждого пароля можно задать время его жизни, и к примеру, раз в полгода напоминать о необходимости смены пароля.

По поводу последнего пункта, получится не совсем чистый генератор "на лету", так как сроки окончания паролей все равно надо хранить. Впрочем, их тоже можно зашифровать с помощью главного пароля.

6

Re: AHK: Генератор паролей, ревизия кода.

Stremin пишет:

скрипт писался в первую очередь для знакомства с AHK ...
То есть, если хендлы будут иметь размерность Ptr, не будет видно, что функции позаимствованы с офф.форума?

Нет смысла начинать знакомиться с AHK с того, в чём не разобрались. Это всё равно, что начинать знакомиться с математикой с интегралов. Советую взяться за что-нибудь попроще.

Если размерность указана неверно, то нет гарантии, что всё сработает правильно на 64-битных версиях интерпретатора.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

7

Re: AHK: Генератор паролей, ревизия кода.

Irbis
Огромное спасибо за весьма ценные конструктивные замечания.
Смена пароля, действительно `узкое место` данного решения. И хотя мне крайне не хотелось хранить пароли, в каком бы то ни было виде, без этого видимо не обойтись.

teadrinker
Сожалею отсутствию у вас чувства иронии. Её дефицит является серьёзным барьером для взаимопонимания.
За совет `заняться чем попроще`, спасибо. В благодарность хотел бы дать и вам совет: советую не советовать такие советы, особенно тогда, когда преследуемый результат получен. Чаще всего они пропадут втуне.

8

Re: AHK: Генератор паролей, ревизия кода.

Stremin пишет:

Сожалею отсутствию у вас чувства иронии.

Ну, ирония бывает разного качества, как и код. Особенно это заметно, когда человек пытается иронизировать на темы, в которых мало что понимает.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

9

Re: AHK: Генератор паролей, ревизия кода.

Ещё раз, всем большое спасибо, за потраченное на меня время. В целом думаю на этом, обсуждение можно закруглить.

PS teadrinker согласно правилам данного ресурса п.2.1, 2.6, ваше сообщение никак не относится к обсуждаемой теме, т.е. является оффтопом. Вы сами себя забаните?

10

Re: AHK: Генератор паролей, ревизия кода.

Stremin, подобное общение с модератором уже не раз приводило к быстрому бану участника. Надеюсь, намёк понят, и ирония здесь отсутствует.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

11 (изменено: Indomito, 2017-05-31 04:47:47)

Re: AHK: Генератор паролей, ревизия кода.

Stremin
А чем дело закончилось?
Дописал скрипт или нет?

Открытых протоколов генерации паролей много... могу штук 5 - 7 дать, но проблема в том, что они открыты, а значит придется вспоминать(мне)/изучать криптологию и криптоанализ.

Я просто не хочу всё писать с "нуля", но я так сказать лицо заинтересованное.

"На каждое действие есть равная ему противодействующая критика." Постулат Харриссона
OS Windows 7 x64
AutoHotkey v1.1.32.00 - November 24, 2019
Click to Download