RosettaCodeData/Task/Password-generator/8086-Assembly/password-generator.8086
2023-07-01 13:44:08 -04:00

274 lines
7.9 KiB
Text

cpu 8086
bits 16
;;; MS-DOS syscalls
gettim: equ 2Ch ; Get system time
write: equ 40h ; Write to file
exit: equ 4Ch ; Exit with return code
;;; MS-DOS process data
arg: equ 80h ; Command line argument (length + string)
;;; BIOS calls
conout: equ 0Eh ; Write character to console
vstate: equ 0Fh ; Get current video state
section .text
org 100h
mov ah,gettim ; Seed the RNG using the system time
int 21h ; (in case of no argument)
mov [rnddat],cx
mov [rnddat+2],dx
mov si,arg ; See if we have any arguments
lodsb
test al,al
jnz hasarg
jmp usage ; If not, print usage string
;;; Parse the command line arguments
hasarg: xor bx,bx ; We do, zero-terminate the string
xchg al,bl
mov [si+bx],al
mov di,count ; Place to start reading arguments
doarg: lodsb ; Get argument byte
.chr: test al,al ; Zero?
jnz .arg ; If not, there are arguments left
jmp check ; If so, we're done
.arg: cmp al,' ' ; Space?
je doarg ; Then get next character
cmp al,'/' ; Option?
je opt
dec byte [nargs] ; Otherwise, it's an argument
jnz rdnum ; Read a number if we still need one
jmp usage ; Otherwise, incorrect arguments
rdnum: xor bp,bp ; Place to keep number
.chr: mov bl,al ; Keep the character in case not a digit
sub al,'0' ; Make into digit
cmp al,9 ; Valid digit?
ja .done ; If not, done with number
add bp,bp ; Multiply accumulator by 10
mov dx,bp
add bp,bp
add bp,bp
add bp,dx
xor ah,ah ; Then add the digit
add bp,ax
lodsb ; Read next digit
jmp .chr
.done: mov ax,bp ; Write the number into memory
stosw
mov al,bl ; Restore the character (next argument)
jmp doarg.chr
opt: lodsb ; Get option character
or al,32 ; Make lowercase
cmp al,'e' ; E?
je .e
cmp al,'s' ; S?
je .s
jmp usage ; If not, invalid argument
.e: inc byte [excl] ; /E: turn on exclusion
jmp doarg
.s: lodsb ; /S: should be followed by '='
cmp al,'='
je .s_ok
jmp usage ; If not, invalid argument
.s_ok: xor bp,bp ; DX:BP = RNG state
xor dx,dx
.hex: lodsb ; Get (potential) hex digit
mov ah,al ; Keep it around
call hexdgt ; Parse hexadecimal digit
jnc .hdone ; If invalid, we're done
shl bp,1 ; Make room for it in the state
rcl dx,1 ; Because it's a 32-bit value,
shl bp,1 ; which is stored in two 16-bit registers,
rcl dx,1 ; we have to shift and rotate the bits
shl bp,1 ; one by one.
rcl dx,1
shl bp,1
rcl dx,1
or dl,al ; Finally, add in the hexadecimal digit
jmp .hex
.hdone: mov [rnddat],bp ; Store random seed
mov [rnddat+2],dx
mov al,ah ; Restore the next character
jmp doarg.chr
;;; Check the arguments
check: dec byte [nargs] ; Were all arguments used?
jz .argok
jmp usage
.argok: mov cx,[count] ; CX = count
mov bp,[length] ; BP = length
test cx,cx ; Sanity check (count must not be zero)
mov si,errmsg.cnt
jnz .cntok
jmp error
.cntok: cmp bp,4 ; Length must be at least four
mov si,errmsg.len
jae .len2
jmp error
.len2: cmp bp,255 ; Length must be no more than 255
mov si,errmsg.lmax
jbe mkmask
jmp error
;;; Make a bitmask in which BP fits (for generating random
;;; numbers in the right range)
mkmask: mov cx,bp
mov bx,cx
.loop: shr cx,1
or bx,cx
test cx,cx
jnz .loop
stc
rcl bx,1
mov [lmask],bl ; Password <= 255 chars
;;; Generate a password
genpwd: lea cx,[bp-4] ; Get length minus four
mov di,buffer ; Write password into buffer
mov dl,[excl] ; Exclusion flag
mov bx,lc ; A lowercase letter,
mov ah,lc.len
call rndchr
mov bx,uc ; An uppercase letter
call rndchr ; (same length of course)
mov bx,dgt ; A digit,
mov ah,dgt.len
call rndchr
mov bx,sym ; And a symbol
mov ah,sym.len
call rndchr
test cx,cx ; If CX=0, we need no extra characters
jz .done
mov bx,cr ; Otherwise, we need CX extra characters
mov ah,cr.len
.loop: call rndchr
loop .loop
.done: mov ax,0A0Dh ; End with a newline
stosw
;;; Swap the first four characters with random characters
;;; from the password, so the four necessary characters won't
;;; necessarily be at the beginning.
swap: mov dx,bp ; Get length
mov dh,[lmask] ; Load the mask
mov si,buffer ; Password buffer
xor bh,bh
xor ah,ah ; Starting at zero
.loc: call rand ; Get random byte
and al,dh ; Mask off unused bits
cmp al,dl ; Result within password?
jae .loc ; If not, get another random number
mov bl,ah ; Load first character in CL
mov cl,[si+bx]
mov bl,al ; Load random character in CH
mov ch,[si+bx]
mov [si+bx],cl ; Write them back the other way around
mov bl,ah
mov [si+bx],ch
inc ah ; Next character
cmp ah,4 ; Are we there yet?
jb .loc ; If not, do another character.
;;; Write password to standard output
xor bx,bx ; File handle 0 = stdout
lea cx,[bp+2] ; Length = password length + 2 (for newline)
mov dx,si ; Location of password
mov ah,write ; Write the password
int 21h
jnc .ok ; Carry clear = success
mov si,errmsg.wrt ; Otherwise, print error message
jmp error
.ok: dec word [count] ; Need any more passwords?
jz stop ; If not, stop
jmp genpwd ; Otherwise, make another password
;;; Quit with return code 0 (success)
stop: mov ax,exit<<8
int 21h
;;; Generate a random character. BX=table, AH=length,
;;; DL=exclusion on/off
rndchr: call rand ; Random number
and al,7Fh ; from 0 to 127
cmp al,ah ; Within valid length?
jae rndchr ; If not, get new random number
xlatb ; Otherwise, get character from table
test dl,dl ; Is exclusion on?
jz .out ; If not, just store it
push cx ; Otherwise, keep CX around,
push di ; and DI,
mov cx,ex.len ; See if character is in exclusion table
mov di,ex
repne scasb
pop di ; restore DI
pop cx ; Restore CX
je rndchr ; And if it was found, get another character
.out: stosb ; Store in password buffer
ret
;;; Random number generator using XABC algorithm
;;; Returns random byte in AL
rand: push cx
push dx
mov cx,[rnddat] ; CH=X CL=A
mov dx,[rnddat+2] ; DH=B DL=C
inc ch ; X++
xor cl,ch ; A ^= X
xor cl,dl ; A ^= C
add dh,cl ; B += A
mov al,dh ; C' = B
shr al,1 ; C' >>= 1
xor al,cl ; C' ^= A
add al,dl ; C' += C
mov dl,al ; C = C'
mov [rnddat],cx
mov [rnddat+2],dx
pop dx
pop cx
ret
;;; If AL is a hexadecimal digit in ASCII, set AL to be its
;;; value. Carry flag set if digit was valid.
hexdgt: or al,32 ; Make letter lowercase (numbers unchanged)
sub al,'0' ; Subtract 0
cmp al,10 ; If it is a valid digit now, we're done
jc .out
sub al,39 ; Otherwise, correct for
cmp al,17
.out: ret
;;; Write the help message, and stop with return code 2
usage: mov si,help
;;; Write an error message, directly to the console, bypassing
;;; DOS to ensure it does not end up in the redirected output.
;;; Then, stop with return code 2 (failure)
error: mov ah,vstate ; Retrieve current video state
int 10h ; (this sets BH = current page)
.loop lodsb ; Get string byte
test al,al ; Zero?
jz .out ; Then we have reached the end
mov ah,conout ; Otherwise, write the character
int 10h
jmp .loop ; And get another one
.out: mov ax,exit<<8|2 ; Stop with error code 2
int 21h
section .data
nargs: db 3 ; We need two non-option arguments
excl: db 0 ; Exclusion is off by default
cr: ;;; Password characters
lc: db 'abcdefghijklmnopqrstuvwxyz'
.len: equ $-lc
uc: db 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
.len: equ $-uc
dgt: db '0123456789'
.len: equ $-dgt
sym: db '!"#$%&()*+,-./:;<=>?@[]^_{|}~',39 ; 39=single quote
.len: equ $-sym
cr.len: equ $-cr
ex: db 'Il1O05S2Z' ; Excluded characters
.len: equ $-ex
;;; Help message
help: db 'PASSGEN [/S=seed] [/E] count length',13,10
db 9,'generate <count> passwords of length <length>',13,10
db 13,10
db 9,'/S=seed: set RNG seed (hexadecimal number)',13,10
db 9,'/E: exclude visually similar characters',13,10
db 0
errmsg: ;;; Error messages
.len: db 'Minimum password length is 4.',0
.lmax: db 'Maximum password length is 255.',0
.cnt: db 'At least one password must be generated.',0
.wrt: db 'Write error.',0
section .bss
count: resw 1 ; Amount of passwords to generate
length: resw 1 ; Length of passwords
lmask: resb 1 ; Mask for random number generation
rnddat: resb 4 ; RNG state
buffer: resb 257 ; Space to store password