Table of Contents
The symbol returns the reader to the top of the Table of Contents.
1. Background
I recently published a WinForm application that generated passwords ( WinForm Generate Password Tool [^]). As I indicated in that article, I was considering writing the same tool as a WebForm application. This article presents the results.
2. Introduction
Because some readers may take issue with the design and implementation of this tool, I wish to quote John Walker [^].
2.1. Why Encrypt with JavaScript?
At first glance, JavaScript may seem an odd choice for implementing encryption. These programs are rather large and complicated, and downloading and running them them takes longer than would be required for a Java applet or to access a CGI program on a Web server. I chose JavaScript for two reasons: security and transparency.
Security. The sole reason for encryption is to protect privacy. This means the process cannot involve any link whose security is suspect. If messages were encrypted by a Web server, they would have to pass over the Internet, where any intermediate site might intercept them. Even if some mechanism such as secure HTTP could absolutely prevent the data's being intercepted, you'd still have no way to be sure the site which performed the encryption didn't keep a copy in a file, conveniently tagged with your Internet address.
In order to have any degree of security, it is essential that all processing be done on your computer, without involving any transmission or interaction with other sites on the Internet. A Web browser with JavaScript makes this possible, since the programs embedded in these pages run entirely on your own computer and do not transmit anything over the Internet. Output appears only in text boxes, allowing you to cut and paste it to another application. From there on, security is up to you.
Transparency. Any security-related tool is only as good as its design and implementation. Transparency means that, in essence, all the moving parts are visible so you can judge for yourself whether the tool merits your confidence. In the case of a program, this means that complete source code must be available, and that you can verify that the program you're running corresponds to the source code provided.
The very nature of JavaScript achieves this transparency. The programs are embedded into the Web pages you interact with; to examine them you need only use your browser's "View Source" facility, or save the page into a file on your computer and read it with a text editor; any JavaScript components the pages reference can be similarly downloaded and examined in source code form. JavaScript's being an interpreted language eliminates the risk of your running a program different from the purported source code: with an interpreted language what you read is what you run.
3. Implementation
Unlike the WinForm Tool, the WebForm Tool is more easily able to separate the user interface from the actual password generation. So the password generator can exist separately from other WebForm components. As a result, there are two JavaScript files: one that generates the password (generate_password.js) and one that supports the user interface (ui.js).
3.1. Password Generator
Generation of a password is accomplished by invoking the global entry point generate_password in the GeneratePassword module.
function generate_password ( options )
{
var character_sets = [ ];
character_sets = create_character_sets ( options );
return ( create_password ( character_sets,
options.desired_length ) );
}
The options parameter is a JSON structure that contains the Boolean values for the character sets and the desired length from which the password can be generated.
var options =
{
desired_length: MINIMUM_PASSWORD_CHARACTERS,
lower_case: true,
numbers: true,
symbols: true,
upper_case: true,
all_characters: true,
easy_to_read: false,
easy_to_say: false,
last_no_comma:0
};
The values of the options components are set in the user interface.
The password generator is similar to that found in the WinForm implementation, with changes to convert C# to JavaScript. The major problem that was encountered was the lack of a JavaScript equivalent to the GetBytes method of the C# RNGCryptoServiceProvider. Since I wanted to use only "vanilla" Javascript, I was forced into using the dreaded Math.random function.
function random_integer ( min, max )
{
return Math.floor ( Math.random ( ) * ( max - min ) ) + min;
}
Note that random_integer is an entry point that is local to the GeneratePassword module.
Determining which characters are to be used in password generation occurs in the local entry point create_character_sets. Unfortunately, there is no universally accepted way of including the character class constants (e.g., export or import). So, DIGITS, LOWERCASE, PUNCTUATION, SPECIAL, and UPPERCASE were required to be copied from ui.js.
Other than the conversion from C# to JavaScript, the only difference between the WinForm implementation and the WebForm implementation of create_character_sets is that the structure holding the character classes is now an array (rather than a List).
function create_character_sets ( options )
{
const DIGITS = '0123456789';
const LOWERCASE = 'abcdefghijklmnopqrstuvwxyz';
const PUNCTUATION = '!#%&()*,-./:,?@[\]_{}"\'';
const SPECIAL = '!@#$%^&*()+=~[:\'<>?,.|';
const UPPERCASE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
var character_sets = [ ];
var digits = "";
var lowercase = "";
var special = "";
var uppercase = "";
if ( options.numbers )
{
digits = DIGITS;
}
if ( options.lower_case )
{
lowercase = LOWERCASE;
}
if ( options.symbols )
{
special = SPECIAL;
}
if ( options.upper_case )
{
uppercase = UPPERCASE;
}
if ( options.all_characters )
{
}
else if ( options.easy_to_say )
{
digits = "";
special = "";
}
else if ( options.easy_to_read )
{
if ( ( digits !== null ) && ( digits.length > 0 ) )
{
digits = digits.replace ( "0", "" ).
replace ( "1", "" );
}
if ( ( uppercase !== null ) && ( uppercase.length > 0 ) )
{
uppercase = uppercase.replace ( "O", "" ).
replace ( "I", "" );
}
if ( ( lowercase !== null ) && ( lowercase.length > 0 ) )
{
lowercase = lowercase.replace ( "o", "" ).
replace ( "l", "" ).
replace ( "i", "" );
}
if ( ( special !== null ) && ( special.length > 0 ) )
{
special = special.replace ( "!", "" ).
replace ( "|", "" );
}
}
character_sets.length = 0;
if ( uppercase.length > 0 )
{
character_sets.push ( uppercase );
}
if ( lowercase.length > 0 )
{
character_sets.push ( lowercase );
}
if ( digits.length > 0 )
{
character_sets.push ( digits );
}
if ( special.length > 0 )
{
character_sets.push ( special );
}
return ( character_sets );
}
With the character set now reflecting the user's choices, the creation of a new password can commence. As with the WinForm implementation, the WebForm implementation uses a modified version of the password generator algorithm found in kitsu.eb's answer in Generating Random Passwords [^].
The major change to that paradign was to incorporate the character_sets data structure. The structure was passed to create_password as the character_sets parameter. Recall that the character_sets structure contains only those character classes that should participate in the generation of a password. create_password is a local entry point.
function create_password ( character_sets,
desired_length )
{
var bytes = [ ];
var characters = "";
var i = 0;
var index = -1;
var password = "";
for ( i = 0; ( i < desired_length ); i++ )
{
bytes [ i ] = random_integer ( 0, 128 );
}
for ( i = 0; ( i < bytes.length ); i++ )
{
var b = bytes [ i ];
index = random_integer ( 0, character_sets.length );
characters = character_sets [ index ];
password += characters [ b % characters.length ];
}
return ( password );
}
The intermediate variable characters was introduced for readability. I believe the alternative code is unintelligible.
3.2. User Interface
The vast amount of code for this project is the user interface, through which the user's intentions are captured. All screen manipulations occur through methods located in ui.js.
The reader should be aware that both the test harness (to the left in the preceding figure) and the generate password (to the right in the preceding figure) exist in the same html file (index.html). The pertinent HTML is
<body>
<div class="container"
style="width:99%;
background-color:#B0E0E6;">
<div class="subcontainer"
id="test_harness_div"
style="width:100%;
display:block;
background-color:#B0E0E6;
padding-bottom:25px;">
<h3>Test Generate Password</h3>
<button class="button blue_button"
style="display:block;
margin: auto;
margin-bottom:1em;"
onclick="UI.toggle_display('test_harness_div','generate_password_div')">
Generate Password
</button>
:
:
</div>
<div class="subcontainer"
id="generate_password_div"
style="width:100%;
background-color:#B0E0E6;
display:none;">
<div style="width:99%;
margin-left:0.5em;">
:
:
The pertinent CSS is
body
{
padding-left:10px;
margin:1em;
width:350px;
}
.container
{
position: relative;
}
.subcontainer
{
position: absolute;
}
and toggle_display is
function toggle_display ( div_1_id, div_2_id )
{
var div_1 = document.getElementById ( div_1_id );
var div_2 = document.getElementById ( div_2_id );
div_1.style.display = ( div_1.style.display == "none" ?
"block" :
"none");
div_2.style.display = ( div_2.style.display == "none" ?
"block" :
"none");
}
toggle_display is the handler for the onclick event for the Generate Password button in the test harness and for the Cancel and Accept buttons in the generate password.
3.2.1. Collecting User Preferences
In order to generate the user's password, the user's preferences need to be collected. This is accomplished by the generate password user interface.
In addition to the generated password, a most important product of the user interface is the options JSON structure. This structure records the user preferences that are used to actually generate a password.
var options =
{
desired_length: MINIMUM_PASSWORD_CHARACTERS,
lower_case: true,
numbers: true,
symbols: true,
upper_case: true,
all_characters: true,
easy_to_read: false,
easy_to_say: false,
last_no_comma:0
};
The initial settings require a six-character (MINIMUM_PASSWORD_CHARACTERS) password that contains uppercase, lowercase, numeric, and special characters.
3.2.1.1. Desired Length
The desired length, in characters, of the password is specified through an input element with a type of number. The set_desired_length handler for the onclick event is:
function set_desired_length ( up_down_value )
{
options.desired_length = up_down_value;
regenerate_password ( );
}
When a new password length recorded in options, the password is regenerated.
3.2.1.2. Desired Character Classes
The character classes are specified through input elements with a type of check. Check boxes were chosen to allow more than one character class to be required. The set_checkbox_checked handler for the onclick event is:
function set_checkbox_checked ( checkbox_id,
message_id )
{
var in_error = false;
var checkbox = document.getElementById ( checkbox_id );
var ischecked = checkbox.checked;
var message = document.getElementById ( message_id );
message.style.visibility = "hidden";
switch ( checkbox.id )
{
case "lowercase":
options.lower_case = ischecked;
break;
case "numbers":
options.numbers = ischecked;
break;
case "symbols":
options.symbols = ischecked;
break;
case "uppercase":
options.upper_case = ischecked;
break;
default:
in_error = true;
message.innerHTML = "Unrecognized CheckBox " + checkbox.id;
break;
}
if ( !in_error )
{
if ( !( options.lower_case ||
options.numbers ||
options.symbols ||
options.upper_case ) )
{
in_error = true;
message.innerHTML =
"At least one checkbox must be checked";
message.style.visibility = "visible";
}
}
if ( !in_error )
{
regenerate_password ( );
}
}
At least one check box must be checked; otherwise an error message will be displayed. When a check box is checked or unchecked, the password is regenerated.
3.2.1.3. Desired Character Restrictions
Restrictions on the character classes are imposed through input elements with a type of radio. Radio boxes were chosen to allow only one restriction to be imposed at a time. The set_radio_checked handler for the onclick event is:
function set_radio_checked ( radiobutton_name,
message_id )
{
var in_error = false;
var message = document.getElementById ( message_id );
var radio_buttons =
document.getElementsByName ( radiobutton_name );
message.style.visibility = "hidden";
for ( var i = 0; ( i < radio_buttons.length ); i++ )
{
var ischecked = radio_buttons [ i ].checked;
switch ( radio_buttons [ i ].id )
{
case "allcharacters":
options.all_characters = ischecked;
break;
case "easytoread":
options.easy_to_read = ischecked;
break;
case "easytosay":
options.easy_to_say = ischecked;
break;
default:
in_error = true;
message.innerHTML = "Unrecognized RadioButton " +
radio_buttons [ i ].id;
message.style.visibility = "visible";
break;
}
}
if ( !in_error )
{
regenerate_password ( );
}
}
In order to maintain a valid representation of the state of the radio buttons in the options JSON structure, the state of all radio buttons must be recorded. For unlike check boxes, only one radio button may be recorded as checked. When a radio button is checked or unchecked, the password is regenerated.
3.2.2. Exiting Password Generation
Whenever password generation is cancelled or a password is accepted, the user is returned to the Test Generate Password display. Again, this is accomplished by invoking toggle_display. If the password generation is cancelled, the display is the same as the initial display of Test Generate Password. However, if the password is accepted, a revised form is displayed.
In addition to displaying the accepted password, the user can copy the returned password to the Clipboard. This functionality is provided so that the password can be futher copied into another textbox.
4. References
5. Conclusion
I have presented a WebForm that provides users with the ability to generate passwords.
6. Browser Compatibility
Neither IE nor Safari toggle between <div>s, making the user interface unworkable. Edge hides the updown portion of the <input type="number"...> control until the mouse hovers over it.
7. Development Environment
The WebForm Generate Password tool was developed in the following environment:
Microsoft Windows 7 Professional SP 1 |
Microsoft Visual Studio 2008 Professional SP1 |
JavaScript Lint 0.3.0 (JavaScript-C 1.5 2004-09-24) |
Firefox 68.0.2 |
8. History
10/20/2019 | Original article |