Click here to visit our sponsor
Free Advertising from Click2Net!


Система шифрования Win3.11/Win95 глазами паталогоанатома


Сразу оговариваюсь, что все, что ниже будет сказано, имеет отношение только к Win3.11 и Win95, причем Win95 без установленного Service Pack 1. У OSR2 и Win98, а также у Win95 с установленным Service Pack 1, другая, хотя и похожая система шифрования, которая не имеет такого изобилия и разнообразия дыр и поэтому менее интересна для обсуждения. Отличить обе системы друг от друга можно по магическим числам в начале PWL-файла : Win3.11 \ Win95 без Service Pack 1 / B0 46 4D 4E (видны буквы "MFN") Win95 с Service Pack 1 \ OSR2 > E3 82 85 96 (в альтернативной кодировке Win98 / выглядит как "yВЕЦ") Все, что далее будет сказано имеет отношение только к PWL-файлам, имеющим сигнатуру "MFN". Для начала, бросим беглый взгляд внутрь PWL-файла, чтобы получше ориентироваться в этой эклектической конструкции.

Формат PWL-файла

Содержимое PWL-файла производит весьма мутное впечатление. Строгая документация от MicroSoft по этому вопросу отсутствует due to security (саркастический смешок) reason, а сам я разбирался лишь в меру необходимости. Поэтому правильнее озаглавить - примерный формат PWL-файла, в предположении, что в нем не более 16-и ресурсов (похоже, что PWL-файл может содержать только кратное 16-и число ресурсов, используемых и неиспользуемых, но мне ни разу не попадался экземпляр более чем с 16-ю ресурсами). 0000: 4 байта - магическое число или сигнатура. Просто безполезное число. В разных форматах PWL-файлов оно разное. Чем и помогает их различать : B0 46 4D 4E - Win3.11/Win95 E3 82 85 96 - OSR2/Win98
0004: Дв. слово - очень странный счетчик. Что-то явно считает, но что ?
0008: Таблица из 256-и байт. Гордо называется Resource Link Index. Странная таблица. Ясно, что нулевой байт означает неиспользованный элемент. Но какая польза с ненулевого байта - не совсем ясно. Если в этой таблице встречается ненулевой байт N, то это означает, что в следующей таблице используется (не равен FF) элемент по смещению 108h+N. Но всю полезную информацию можно сразу извлечь из второй таблицы, не обращаясь к первой.
0108: Безхозный нулевой байт. Никому не мешает.
0109: Таблица Resource Key Entry из 255-и байт. Теперь неиспользуемый элемент обозначается уже байтом FF. Вероятно между созданием первой и второй таблиц программистом был пропущен стаканчик. Если в этой таблице находится M байтов N, не равных FF, то это означает, что в ресурсе номер N содержится M парольных записей. Обшее число не равных FF байт в таблице равно общему числу парольных записей. Отсюда ясно, что в PWL-файле не может быть более 255-и парольных записей и более 255-и ресурсов, их содержащих.
0208: 20 байт - имя пользователя в верхнем регистре, дополненное справа нулевыми байтами (русские буквы в Альтернативной кодировке). Данное поле служит для странного алгоритма определения подлинности пароля. Имя пользователя можно посмотреть в файле system.ini, в свойстве Password Lists и, если оно не длиннее восьми символов, то оно будет совпадать с именем PWL-файла.
021C: 17 слов - таблица указателей на начала ресурсов. 21C:Смещение в файле начала ресурса 0, относительно начала файла
21E:Смещение в файле начала ресурса 1, относительно начала файла
220:Смещение в файле начала ресурса 2, относительно начала файла
...

23A:Смещение в файле начала ресурса F, относительно начала файла
23C:Смещение первого байта за концом файла (равно длине файла).
Обе записи 208h-21Bh и 21Ch-23Dh образуют единое поле, которое
зашифровано гаммой, накладываемой начиная со смещения 208h.
023E: Ресурс 0
Ресурс 1
Ресурс 2
... Ресурс F
В одном ресурсе может быть несколько парольных записей, следующих одна за другой. Первое слово каждой записи представляет собой длину записи, включая и это слово. Признаком конца цепочки записей является нулевое слово. Таким образом пустой ресурс - это просто нулевое слово. Тогда ясно, что если PWL-файл имеет длину 606 байт, то все ресурсы в нем пустые, т.к. 23Eh + 16*2 = 25Eh = 606. Каждый ресурс зашифрован гаммой, которая накладывается, начиная с его начала. PWL-файл шифруется простым гаммированием, гамма генерируется алгоритмом RC4. При первой регистрации пользователя запрашивается пароль. Он приводится к верхнему регистру и сворачивается в ключ (двойное слово). Из этого ключа порождается гамма (псевдослучайная последовательность нулей и единиц). Эта гамма сложением по модулю два накладывается на каждый из ресурсов с его начала и зашифровывает их. Аналогично ресурсам зашифровывается поле 208h-23Dh, где хранится имя пользователя и таблица указателей на начала ресурсов. При последующих регистрациях данным пользователем запрашивается пароль. Он приводится к верхнему регистру, опять сворачивается в ключ из которого опять порождается гамма. Если этой гаммой имя пользователя в поле 208h-21Bh расшифровывается правильно, то пароль считается введенным правильно. После чего расшифровываются таблица указателей на начала ресурсов и сами ресурсы PWL-файла. Расшифровка производится вторичным наложением гаммы сложением по модулю два (используется обратимость сложения по модулю два, то есть тот факт, что (X XOR Y) XOR Y = X ). Если имя пользователя не расшифровывается правильно, то пароль считается неправильным. Таким образом проверка правильности введенного пароля производится по совпадению первых 20-и байт порожденной из него гаммы с первыми 20-ю байтами гаммы от правильного пароля. Этот алгоритм определения подлинности пароля является весьма оригинальным, т.к. при этом нигде не сохраняется ни зашифрованный пароль, ни хеш-функция (необратимое преобразование) пароля. Но, в то-же время, на удивление, нелепо реализованным. Ведь поскольку имя пользователя известно ЗАРАНЕЕ, то первые 20 байт гаммы тривиально вычисляются. Но, т.к. ТА-ЖЕ гамма накладывается на каждый ресурс (отсутствие смены гаммы при шифровании разных полей - это основная ошибка применения алгоритма RC4 в данном случае), то можно расшифровать и первые 20 байт каждого ресурса ! PWL-файл имеет избыточную информацию - есть указатели на начала ресурсов, но есть и длины записей в ресурсах и из одного можно вычислять другое. Если в ресурсах не более одной записи, то длина ресурса есть первое слово ресурса плюс два (длина первой записи ресурса плюс длина нулевого слова). Определяя по началу и длине данного ресурса начало следующего, рассчитывается вся таблица указателей на начала ресурсов в поле 21Ch-23Dh. В результате еще 2*17 = 34 байта добавляются к рассчитанной гамме. Если в ресурсах более одной записи, то начало следующего ресурса все равно можно найти с некоторой долей вероятности (после наложения гаммы второй байт ресурса будет равен второму байту гаммы, т.к. длина записей в ресурсе редко превышает 255, а 0 XOR X = X ). Этот алгоритм реализован в известной программе Glide, которая рассчитывает 54 первых байта гаммы и расшифровывает по 54 первых байта каждого ресурса. Причем немедленно и без какого либо перебора вариантов, что сводит прочность системы шифрования Win95 к НУЛЮ (под прочностью системы шифрования понимается количество вариантов, которые необходимо перебрать для ее гарантированного вскрытия). Между тем, достаточно было накладывать гамму на ресурсы, не используя первых засвеченных ее байт, чтобы такой огромной прорехи не было.

Алгоритм генерации ключа по паролю (c) Microsoft

Имеем Key - двойное слово и пароль до 20-и символов. 1) Обнулить Key.
2) Привести пароль к верхнему регистру (русские буквы приводятся к верхнему регистру в Альтернативной кодировке).
3) Для каждого символа пароля, начиная с первого : а) прибавить код символа к Key б) повернуть Key влево 7 раз.
4) На прощание еще раз вращать Key влево 7 раз. (программистам на Си этот шаг можно не делать, считая, что нулевой байт, завершающий строку с паролем, тоже является его символом и повторить цикл 3 на один раз больше) Ключ используется для инициализации генератора псевдослучайных чисел (алгоритма RC4). Для каждого ключа RC4 порождает уникальную битовую последовательность (гамму). Алгоритм сопоставления ключа паролю слаб тем, что при выбранной длине ключа в двойное слово, множество различных ключей (2^32) оказывается неизмеримо меньше множества различных паролей. Это означает, что существуют пароли, которые Win95 не отличает друг от друга. Например пароли "ARJEW" и "QRJEV" свернутся в один и тот-же ключ A8B6:C694, породят одну и ту-же гамму и поэтому для Win95 полностью эквивалентны ! В пароле могут использоваться 100 различных символов (26 латинских букв + 10 цифр + 32 спецсимвола + 32 русские буквы). Отсюда 1+100+100^2+100^3+100^4+...+100^14 ~ 100^14 = 10^28 - множество различных паролей от пустого до 14-символьных. 2^32 = 4*((2^10)^3) ~ 4*((10^3)^3) = 4*10^9 - множество различных ключей. (здесь везде ~ - примерно равно). Т.е. множество возможных ключей на много порядков меньше множества возможных паролей. Это делает совершенно бессмысленными допускаемые в Win95 длинные пароли. Поскольку 2^32 < 100^5 , то эффективная длина пароля соответствует только ПЯТИ символам ! Это, правда, совершенно не означает, что для каждого пароля найдется пятисимвольный эквивалент, т.к. множество паролей при таком алгоритме очень неравномерно отображается на множество ключей. Но мне удалось построить алгоритм, который для любого значения ключа немедленно, то есть без перебора, вычисляет подходящий девятисимвольный пароль из следующего множества символов - "@ABCDEFGJKNOPQRSTUVWX". Так что доказано, что ЛЮБОМУ паролю в Win95 найдется эквивалент не длиннее ДЕВЯТИ символов ! Поэтому задавать более длинные пароли совершенно бессмысленно ! Пустому паролю соответствует нулевое значение ключа. Возникает интересная задача: существует-ли пароль, который бы Win95 не отличала от пустого пароля ? Удивительно, но существует !!! Мне удалось найти 16 девятисимвольных таких паролей, состоящих из одних латинских букв. Из них наиболее симпатичный "FFFFKKKKL". Также нашел 8 восьмисимвольных с одним специальным сиволом "{" - например "FFFO{KKL" или "GGGO{CCD" и поэтому менее красивых. Более короткие такие пароли невозможны.

Алгоритм синтеза пароля по ключу (c) Иванов А.Р.

PRINT_PASSWORD PROC NEAR
;На входе DX:AX - ключ
;Печатает девятисимвольный пароль на станд. выход
;Пароль состоит из символов @ABCDEFGJKNOPQRSTUVWX
;Портит AX BX CX DX SI
PUSH DS
PUSH CS
POP DS
MOV Masks[8],0010111B
MOV CX,32-14
CALL ROL_DW
MOV BX,0000000001010100B
TEST DX,DX
JNZ @@2
CMP AX,BX
JNE @@1 ;Jcc не меняют флагов
INC Masks[8] ;INC не меняет флага переноса
@@1: SBB BX,CX ;CX=0
@@2: SUB AX,0010101000000000B
SBB DX,0100101010010101B
SBB AX,BX
SBB DX,CX ;CX=0
SBB AX,CX ;CX=0
MOV SI,8
Implant: MOV BL,AL
AND BL,Masks[SI]
OR Password[SI],BL
MOV CL,32-7 ;CH=0
CALL ROL_DW
DEC SI
JNS Implant ;Цикл 9 раз
MOV DX,OFFSET Password
MOV AH,9 ;Вывод строки 9-й
INT 21h ;функцией MS-DOS
POP DS
RET
;Подпрограмма вращает двойное слово DX:AX влево CX раз
ROL_DW: SHL AX,1
RCL DX,1
ADC AL,CH ;CH=0
LOOP ROL_DW
RET ;На выходе CX=0

PassWord DB 1000010B,1000010B,1000010B,1000010B
DB 1000000B
DB 1000000B,1000000B,1000000B,1000000B
DB '$'
Masks DB 0001101B,0001101B,0001101B,0001101B
DB 0001111B
DB 0010111B,0010111B,0010111B,?
PRINT_PASSWORD ENDP
То, что множество возможных значений ключей много меньше множества различных значений паролей, означает, что среднее время необходимое для расшифровки чужого PWL-файла можно значительно снизить, если перебирать не пароли, а ключи. Оптимизированная по быстродействию функция алгоритма RC4 может вычисляться со скоростью 50000 crypt/sec на машине класса Pentium-166. Если вычислять с такой скоростью, то 2^32 вариантов перебираются менее, чем за сутки. Подобрав ключ, можно без перебора сгенерировать подходящий пароль. Этот алгоритм реализован в моей программе PWL_Key. Таким образом вся система шифрования PWL-файла может ГАРАНТИРОВАННО вскрыта МЕНЕЕ, ЧЕМ ЗА СУТКИ !!! Поскольку Win95 не ограничивает длину пароля снизу, то короткие пароли легко подбирает программа PWL_Hack Владимира Калашникова. В силу того, что для Win95 существуют неразличимые пароли, PWL_Hack может, с некоторой долей вероятности, быстро подобрать короткий пароль-аналог, даже если настоящий пароль был достаточно длинным и не представлял собой слово из словаря. Отсюда следует, что собственно оценить слабым или нет является пароль, по его внешнему виду, в Win95, невозможно. И невозможны никакие рекомендации по выбору надежных паролей. Например, достаточно надежный, на первый взгляд, пароль "gfFo{CkL" вообще соответствует полному отсутствию какого-бы то ни было пароля ! Криптосистема Win95 производит удручающее впечатление, прежде всего нелепым применением криптоалгоритма RC4. Ведь Glide расшифровывает приличные куски или, иногда, даже весь PWL-файл, не перебирая варианты и даже НИ РАЗУ не вычисляя криптографической функции. Более того, алгоритм программы Glide вообще никак не зависит от способа генерации гаммы, то есть от RC4. Надо же умудриться применить криптоалгоритм ТАК, чтобы прочность системы шифрования от этого криптоалгоритма не зависела НИКАК !!! Именно поэтому RC4 здесь и не рассматривается. Это вполне надежный криптоалгоритм, ну и что с того ? Криптосистема Win95 не может обеспечить даже минимума безопасности ! Вероятно, по принципу : раз Win95 - ОС для домохозяек, то и защита в ней тоже от домохозяек.

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

Все люди, которым я показывал набросок этой статьи советовали мне переписать мой алгоритм на Си, но, во первых, я не считаю Си признаком хорошего тона, а во вторых от этого ничего не стало бы понятнее. Ну, не выражается этот алгоритм в ПОНЯТИЯХ ЯЗЫКОВ ВЫСОКОГО УРОВНЯ ! Никак не выражается. Все, конечно, допускает имитацию, но гораздо полезнее попытаться обьяснить смысл совершаемых операций, тем более, что алгоритм является простейшим примером обращения односторонней функции (так называемого построения коллизии этой функции). Но сначала придется сделать лирическое отступление об односторонних функциях (one way functions) этого типа. Не вполне строгое, но помогающее ухватить суть. Односторонняя функция имеет обратную. Но если прямая функция вычисляется легко (представима в виде совокупности элементарных операций), то обратную через элементарные операции выразить невозможно или еще никому не удалось, т.е. она может быть представлена только в виде таблицы. Но, если таблица требует астрономического количества записей (например 2^128 ~ 3*10^28 записей для 128-битной односторонней функции), то ее построение нереально. Тогда единственным способом вычислить эту функцию в обратную сторону для какого-то конкретного значения является многократное вычисление ее в прямую сторону, перебирая все возможные значения аргумента, вплоть до совпадения с этим значением. Этот метод перебора вариантов называется атака Brute force, т.е. штурм грубой силой. Но, если количество возможных вариантов астрономическое, то и время на этот штурм требуется соответствующее (например в среднем 2^128/10000/60/60/24/365/2 ~ 5*10^17 лет для 128-битной односторонней функции, вычисляемой ЭВМ в прямую сторону 10000 раз в секунду). То есть односторонняя функция - это функция, которая имеет обратную, но вычисление обратной лежит за пределами вычислительных возможностей, хотя в прямую сторону она вычисляется легко и быстро. Каждый бит результата односторонней функции представляет собой черезвычайно сложную логическую функцию от каждого из битов аргумента. Поэтому построение коллизии - это почти безнадежная задача, решением которой можно гордиться. Односторонние функции широко применяются в системах аутентификации. Пароли пользователей хранятся в виде результата применения к нему такой функции. Такой результат называется хешированным паролем или хеш-значением пароля (например второе поле файла /etc/passwd в Unix - это хеш, помещенный туда при последней смене пароля). При проверке пароля, хеш-значение, вычисляемое от введенного пароля, сравнивается с хранимым хеш-значением от правильного пароля. При их совпадении пароль считается правильным. Даже если хеш-значение удалось украсть, прямо вычислить из него пароль невозможно в силу односторонности его получения. Поэтому все парольные крякеры действуют, вычисляя хеш-функцию в лоб и тупо перебирая варианты паролей или пробуя слова из словаря. Но вот если бы хеш-функцию удалось обратить ... :) В Win95 длина ключа выбрана малой - 32 бита. Поэтому возможен быстрый подбор правильного ключа перебором вариантов. Если бы ключ получался из пароля хорошей односторонней функцией, то вычислить правильный пароль из правильного ключа было бы невозможно, иначе как полным перебором всех паролей. Но функция получения ключа ИЗЛИШНЕ ПРОСТА, чтобы не попытаться построить ей обратную. Если это удастся, то можно будет вычислять подходящий пароль сразу после быстрого подбора правильного ключа. Посмотрим, как влияют биты символов пароля на биты ключа при его формировании. (для простоты забудем о русских буквах в пароле и будем считать его символы 7-битными). 0000 0000 0000 0000 0000 0000 0000 0000 - пустой пароль
0000 0000 0001 1111 1100 0000 0000 0000 - 1-символьный пароль
0000 1111 1112 2222 2200 0000 0000 0000 - 2-символьный пароль
1111 2222 2223 3333 3300 0000 0000 0111 - 3-символьный пароль
2222 3333 3334 4444 4400 0011 1111 1222 - 4-символьный пароль
(здесь ненулевыми цифрами обозначены биты соответствующего символа, а нулями - нулевые биты) Пока биты просто вставляются в ключ и друг на друга не влияют. Но дальше биты начинают накладываться друг на друга сложением и взаимовлиять друг на друга. 3333 4444 4445 5551 1111 1122 2222 2333 - 5-символьный пароль
5 55
4444 5555 1111 1112 2222 2233 3333 3444 - 6-символьный пароль
5556 6666 66
5111 1111 2222 2223 3333 3344 4444 4555 - 7-символьный пароль
555 6666 6667 7777 77
1222 2222 3333 3334 4444 4455 5511 1111 - 8-символьный пароль
6666 7777 7778 8888 88 55 5666
2333 3333 4444 4445 5551 1111 1122 2222 - 9-символьный пароль
7777 8888 8889 9999 99 5 5566 6666 6777
Для удобства рассуждений повернем ключ от 9-символьного пароля 14 раз вправо - вот так :
5111 1111 2222 2223 3333 3344 4444 4555
555 6666 6667 7777 7788 8888 8999 9999
Все биты ключа от 9-символьного пароля, за исключением старшего, получаются как результат сложения двух бит от разных символов плюс переносы от предыдущих разрядов. А что если фиксировать некоторые биты символов пароля так, чтобы эти символы заведомо были латинскими буквами в верхнем регистре ? Например так : -100 --1- 100- -1-1 00-- 1-10 0--1 -100 --- 10-0 ---1 0-0- --10 -0-- -10- 0--- (здесь 0 и 1 - нулевые и единичные биты) За счет фиксированных бит символы пароля смогут быть только такими : 1-4 символ 100--1- BCFGJKNO
5 символ 100---- @ABCDEFGHIJKLMNO
6-9 символ 10-0--- @ABCDEFGPQRSTUVW
(от использования "@" мне избавиться не удалось). :(
Варьируя 32 остальных бита символов пароля, мы сможем получить любые 32 бита ключа, т.к. каждому биту ключа мы сопоставили один ФИКСИРОВАННЫЙ и один ВАРЬИРУЕМЫЙ бит, т.е. оставили для каждого бита ключа по ОДНОЙ СТЕПЕНИ СВОБОДЫ. Это предположение, как оказалось на практике, выполняется с одним исключением - две комбинации варьируемых бит рождают один и тот-же ключ, а для ключа 0015:0000 подходящей комбинации вообще не находится. Пришлось подобрать для него подходящий пароль "GGGOKCCCX" вручную. Прибавление каждого кода символа пароля к ключу можно считать состоящим из двух сложений : фиксированных бит и вырьируемых бит. Тогда, вычтя из ключа фиксированные биты, мы получим варьируемые и соберем пароль из конкретных символов (из множества "@ABCDEFGJKNOPQRSTUVWX"). Беда в том, что при формировании ключа помимо сложения используется операция вращения. Постараемся от нее избавиться, чтобы формирование ключа выглядело как совокупность одних сложений. Можно перестать думать о вращениях, если ввести совершенно безумное понятие КОЛЬЦЕВОГО ЧИСЛА, т.е. последовательности бит, завернутой в колечко. Прибавление константы к кольцевому числу будет выглядеть так : ADD EAX,Constant ;кольцевое сложение - "ADD + ADC"
ADC EAX,0 ;благодаря команде "ADC ноль" перенос
;перелетает из конца в начало числа
;перенос не может уйти еще на один
;круг, поэтому одного ADC достаточно
Колечко можно считать неподвижным, а биты символов пароля перед сложением сдвигать на соответствующие места. При попытке разобраться в свойствах этих битовых колечек, обнаружилось, что 0(кольцевое) + 1 = 1(кольцевое), но с другой стороны FFFFFFFF(кольцевое) + 1 = 1(кольцевое) тоже !!! После такого открытия у автора этих строк медленно поехала крыша и только этим состоянием он может обьяснить успешный результат. Во всяком случае, дальнейшие рассуждения носят явно нездоровый характер :) Пусть вначале было кольцо из 32-х нулевых бит. Тогда коды первых 7-и символов пароля будут прибавлены к этому кольцу кольцевым сложением, если их биты предварительно сдвинуть на соответствующие позиции вот так : 5111 1111 2222 2223 3333 3344 4444 4555 555 6666 6667 7777 77 Но, при прибавлениях 8 и 9-го символов (и только при них - это можно доказать) возможен перенос за пределы двойного слова, который теряется. В колечке появляется разрыв. Чтобы учесть это обстоятельство, представим себе кольцевое число с ловушкой для переноса. Пусть между какими-то битами кольцевого числа расположена ловушка. Если в нее попадает перенос, то он гибнет и далее не распространяется. Пусть такой перенос называется RIP-переносом (светлая ему память). :) Тогда восьмой символ будет добавлен к ключу кольцевым сложением, если ловушка расположена между 6-ым и 7-ым битами, а биты символа предварительно сдвинуты так : 0000 0000 0000 0000 0088 8888 8000 0000 Девятый символ будет добавлен обычным сложением, которое можно считать кольцевым, если ловушка расположена между 31-ым и 0-ым битами (там где двойное слово склеивается в кольцо), а биты символа никак не сдвинуты : 0000 0000 0000 0000 0000 0000 0999 9999 Но, попадание переносов в ловушки можно рассматривать, как особый случай. В итоге можно свести весь алгоритм формирования ключа к ОДНОЙ ЕДИНСТВЕННОЙ ОПЕРАЦИИ КОЛЬЦЕВОГО СЛОЖЕНИЯ ПРЕДВАРИТЕЛЬНО СДВИНУТЫХ БИТОВ СИМВОЛОВ ПАРОЛЯ с особым случаем, когда за счет гибели переноса результат кольцевого сложения нужно будет слегка подправить. Обратным КОЛЬЦЕВОМУ СЛОЖЕНИЮ (ADD + ADC ноль) будет, разумеется, являться столь-же безумное КОЛЬЦЕВОЕ ВЫЧИТАНИЕ (SUB + SBB ноль). Таким образом, у операции сворачивания пароля в ключ нашлась ОБРАТНАЯ, хотя и осязаемая, в основном, на подсознательном уровне ! Особый случай, который, как оказалось соответствует некоторому диапазону ключей, учитывается небольшой коррекцией процесса такого вычитания. Родившийся в результате обратный алгоритм правильнее отнести к области творчества душевнобольных. :) Я его сам до конца не понимаю, но, тем не менее, считает он правильно. Во всяком случае, когда я его записывал, я как-то доказал его правильность. Только сейчас совершенно не могу вспомнить как. :( Поэтому прежде, чем выставлять его на всеобщее обозрение, тупо проверил все варианты ключей от 0000:0000 до FFFF:FFFF за сутки счета на 486DX-66. Все, до единого, сгенерированные пароли были правильны ! Как удивительно ... :)

Логика (здесь это слово звучит кощунственно) алгоритма

1.) Вращать ключ 14 раз вправо (или 18 раз влево - без разницы). 2.) Если ключ - 0000:0054, то пароль "GGGOKCCCX" {пароль-исключение}. Стоп.
3.) Если ключ в диапазоне 0000:0000 - 0000:0053, то RIP-перенос = 1, в противном случае RIP-перенос = 0.
4.) Кольцевым вычитанием вычесть из ключа (DX:AX) фиксированные биты пароля :
SUB AX,0010101000000000B
SBB DX,0100101010010101B
SBB AX,(0000000001010100B минус RIP-перенос)
SBB DX,0 ;ну разве не прелесть, как заем пробегает по
SBB AX,0 ;кольцу при таком фантастическом вычитании ? :)
Мы получили варьируемые биты пароля.
5.) Имплантировать полученные варьируемые биты пароля в заготовки для символов и напечатать полученный пароль.

В алгоритме осталось обьяснить один штрих. Вместо того, чтобы прямо печатать на экране пароль-исключение "GGGOKCCCX", одна из костант в алгоритме портится так, чтобы именно этот пароль и получался. Так что никакого особого смысла в команде INC Masks[8] нет. Она случайно дает именно такой результат, какой нужен. Но алгоритму это добавило некоторой доли изящества. Тем, кто не знает ассемблера, я приношу свои извинения. Как же все таки записать на языке высокого уровня цепочку
SUB AX,Const_1 SBB DX,Const_2 SBB AX,Const_3 SBB DX,0 SBB AX,0
( или вариант - SUB EAX,Const_1_ SBB EAX,Const_2_ SBB EAX,0 )
без извращений, затеняющих еe трансфинитную :) суть, я так и не додумался. :(