JavaScript 密码生成器有时不包括字符选择?

JavaScript Password Generator Sometimes Not Including Character Selections?

你好堆栈溢出!

这是我第一次在网站上发帖,所以请坦白我和我的问题。我的 class 的任务是使用 JavaScript 单独创建一个密码生成器。值得庆幸的是,我已经让大部分应用程序正常运行,但我遇到了一个问题。

示例: 用户选择密码中包含 8 个字符,并选择包括特殊字符、小写字符和大写字符。生成密码时,有时它不会包含所有字符选择。 (有时它会生成一个包含特殊字符和大写字符的密码,但没有一个小写字符)。

我已经完成这项作业一分钟了,但我的目标是了解我可以做些什么来解决这个问题并完成这个应用程序。我正在考虑可能删除 passwordOptions 对象并将每个选项变成它们自己的数组,你有什么想法?

非常感谢您的任何建议! :D

// passwordOptions contains all necessary string data needed to generate the password
const passwordOptions = {
  num: "1234567890",
  specialChar: "!@#$%&'()*+,^-./:;<=>?[]_`{~}|",
  lowerCase: "abcdefghijklmnopqrstuvwxyz",
  upperCase: "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
};

document.getElementById('generate').addEventListener('click', function() {
  alert(generatePassword());
});

// Executes when button is clicked
let generatePassword = function() {

  // initial state for password information
  let passInfo = "";

  // ask user for the length of their password
  let characterAmount = window.prompt("Enter the amount of characters you want for your password. NOTE: Must be between 8-128 characters");

  // If the character length doesn't match requirements, alert the user
  if (characterAmount >= 8 && characterAmount < 129) {

    // ask if user wants to include integers
    let getInteger = window.confirm("Would you like to include NUMBERS?");

    // if user wants to include numbers
    if (getInteger) {
      // add numerical characters to password data 
      passInfo = passInfo + passwordOptions.num;
    };

    // ask if user wants to include special characters
    let getSpecialCharacters = window.confirm("Would you like to include SPECIAL characters?");

    // if user wants to include special characters 
    if (getSpecialCharacters) {
      // add special characters to password data
      passInfo = passInfo + passwordOptions.specialChar;
    };

    // ask if user wants to include lowercase characters
    let getLowerCase = window.confirm("Would you like to include LOWERCASE characters?");

    // if user wants to include lowercase characters
    if (getLowerCase) {
      // add lowercase characters to password data
      passInfo = passInfo + passwordOptions.lowerCase;
    };

    // ask if user wants to include uppercase characters
    let getUpperCase = window.confirm("Would you like to include UPPERCASE characters?");

    // if user wants to include uppercase characters
    if (getUpperCase) {
      // add uppercase characters to password data 
      passInfo = passInfo + passwordOptions.upperCase;
    };

    // ensure user chooses at least one option
    if (getInteger !=true && getSpecialCharacters !=true && getLowerCase !=true && getUpperCase !=true) {
      // notify user needs to select at least one option
      window.alert("You need to select at least one option, please try again!");
      // return user back to their questions
      return generatePassword();
    };

    // randomPassword is an empty string that the for loop will pass information in
    let randomPassword = "";

    // for loop grabs characterAmount to use
    for (let i = 0; i < characterAmount; i++) {
      //passInfo connects to charAt that uses both Math.floor and random to take the length of passInfo and randomize the results
      randomPassword += passInfo[Math.floor(Math.random() * passInfo.length)];
    };

    // return password results
    return randomPassword;
  }
  // if user's response is invalid
  else {
    // alert user
    window.alert("You need to provide a valid length!");
    // return user back to their questions
    
    /* Removed for testing purposes to break the endless loop. */
    // return generatePassword();
  }
};
<button id="generate">Run</button>

我能想到的最好的方法是从每个 selected 类别中选择一个字符,然后 select 随机选择剩余的字符,最后将 selected 字符洗牌。

你可以这样做:

// passwordOptions contains all necessary string data needed to generate the password
const passwordOptions = {
  num: "1234567890",
  specialChar: "!@#$%&'()*+,^-./:;<=>?[]_`{~}|",
  lowerCase: "abcdefghijklmnopqrstuvwxyz",
  upperCase: "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
};

document.getElementById('generate').addEventListener('click', function() {
  alert(generatePassword());
});

// Executes when button is clicked
let generatePassword = function() {

  // initial state for password information
  let passInfo = "";
  
  // list of chosen characters
  const passChars = [];

  // ask user for the length of their password
  let characterAmount = window.prompt("Enter the amount of characters you want for your password. NOTE: Must be between 8-128 characters");

  // If the character length doesn't match requirements, alert the user
  if (characterAmount >= 8 && characterAmount <= 128) {

    // ask if user wants to include integers
    const getInteger = window.confirm("Would you like to include NUMBERS?");

    // if user wants to include numbers
    if (getInteger) {
      // add numerical characters to password data 
      passInfo += passwordOptions.num;
      // add a number to the list of chosen characters
      passChars.push(getRandomChar(passwordOptions.num));
    };

    // ask if user wants to include special characters
    const getSpecialCharacters = window.confirm("Would you like to include SPECIAL characters?");

    // if user wants to include special characters 
    if (getSpecialCharacters) {
      // add special characters to password data 
      passInfo += passwordOptions.specialChar;
      // add a special character to the list of chosen characters
      passChars.push(getRandomChar(passwordOptions.specialChar));
    };

    // ask if user wants to include lowercase characters
    const getLowerCase = window.confirm("Would you like to include LOWERCASE characters?");

    // if user wants to include lowercase characters
    if (getLowerCase) {
      // add lowercase characters to password data 
      passInfo += passwordOptions.lowerCase;
      // add a lowercase character to the list of chosen characters
      passChars.push(getRandomChar(passwordOptions.lowerCase));
    };

    // ask if user wants to include uppercase characters
    const getUpperCase = window.confirm("Would you like to include UPPERCASE characters?");

    // if user wants to include uppercase characters
    if (getUpperCase) {
      // add uppercase characters to password data 
      passInfo += passwordOptions.upperCase;
      // add an uppercase character to the list of chosen characters
      passChars.push(getRandomChar(passwordOptions.upperCase));
    };

    // ensure user chooses at least one option -- passInfo will be empty if they don't
    if (!passInfo) {
      // notify user needs to select at least one option
      window.alert("You need to select at least one option, please try again!");
      // return user back to their questions
      return generatePassword();
    };

    // while there aren't enough characters
    while(passChars.length < characterAmount) {
      // choose a random char from charInfo
      passChars.push(getRandomChar(passInfo));
    };

    // shuffle the list of characters using Fisher-Yates algorithm
    // see 
    for(let i = passChars.length - 1; i > 0; i--){
      const swapIndex = Math.floor(Math.random() * (i + 1));
      const temp = passChars[i];
      passChars[i] = passChars[swapIndex];
      passChars[swapIndex] = temp;
    };

    // return the password character list concatenated to a string
    return passChars.join("");
  }
  // if user's response is invalid
  else {
    // alert user
    window.alert("You need to provide a valid length!");
    // return user back to their questions
    
    /* Removed for testing purposes to break the endless loop. */
    // return generatePassword();
  }
};

function getRandomChar(fromString){
  return fromString[Math.floor(Math.random() * fromString.length)];
}
<button id="generate">Run</button>

但是,由于密码生成器应该是随机加密的,因此您应该使用 crypto.getRandomValues() 而不是 Math.random()。您可以使用 将其转换为一系列值:

// Generate a random integer r with equal chance in  0 <= r < max.
function randRange(max) {
    const requestBytes = Math.ceil(Math.log2(max) / 8);
    if (!requestBytes) { // No randomness required
        return 0;
    };
    const maxNum = Math.pow(256, requestBytes);
    const ar = new Uint8Array(requestBytes);

    while (true) {
        window.crypto.getRandomValues(ar);

        let val = 0;
        for (let i = 0; i < requestBytes;i++) {
            val = (val << 8) + ar[i];
        };

        if (val < maxNum - maxNum % max) {
            return val % max;
        };
    };
};

您可以像这样将它与上面的代码结合起来:

// passwordOptions contains all necessary string data needed to generate the password
const passwordOptions = {
  num: "1234567890",
  specialChar: "!@#$%&'()*+,^-./:;<=>?[]_`{~}|",
  lowerCase: "abcdefghijklmnopqrstuvwxyz",
  upperCase: "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
};

document.getElementById('generate').addEventListener('click', function() {
  alert(generatePassword());
});

// Executes when button is clicked
let generatePassword = function() {

  // initial state for password information
  let passInfo = "";
  
  // list of chosen characters
  const passChars = [];

  // ask user for the length of their password
  let characterAmount = window.prompt("Enter the amount of characters you want for your password. NOTE: Must be between 8-128 characters");

  // If the character length doesn't match requirements, alert the user
  if (characterAmount >= 8 && characterAmount <= 128) {

    // ask if user wants to include integers
    const getInteger = window.confirm("Would you like to include NUMBERS?");

    // if user wants to include numbers
    if (getInteger) {
      // add numerical characters to password data 
      passInfo += passwordOptions.num;
      // add a number to the list of chosen characters
      passChars.push(getRandomChar(passwordOptions.num));
    };

    // ask if user wants to include special characters
    const getSpecialCharacters = window.confirm("Would you like to include SPECIAL characters?");

    // if user wants to include special characters 
    if (getSpecialCharacters) {
      // add special characters to password data 
      passInfo += passwordOptions.specialChar;
      // add a special character to the list of chosen characters
      passChars.push(getRandomChar(passwordOptions.specialChar));
    };

    // ask if user wants to include lowercase characters
    const getLowerCase = window.confirm("Would you like to include LOWERCASE characters?");

    // if user wants to include lowercase characters
    if (getLowerCase) {
      // add lowercase characters to password data 
      passInfo += passwordOptions.lowerCase;
      // add a lowercase character to the list of chosen characters
      passChars.push(getRandomChar(passwordOptions.lowerCase));
    };

    // ask if user wants to include uppercase characters
    const getUpperCase = window.confirm("Would you like to include UPPERCASE characters?");

    // if user wants to include uppercase characters
    if (getUpperCase) {
      // add uppercase characters to password data 
      passInfo += passwordOptions.upperCase;
      // add an uppercase character to the list of chosen characters
      passChars.push(getRandomChar(passwordOptions.upperCase));
    };

    // ensure user chooses at least one option -- passInfo will be empty if they don't
    if (!passInfo) {
      // notify user needs to select at least one option
      window.alert("You need to select at least one option, please try again!");
      // return user back to their questions
      return generatePassword();
    };

    // while there aren't enough characters
    while(passChars.length < characterAmount) {
      // choose a random char from charInfo
      passChars.push(getRandomChar(passInfo));
    };

    // shuffle the list of characters using Fisher-Yates algorithm
    // see 
    for(let i = passChars.length - 1; i > 0; i--){
      const swapIndex = randRange(i + 1);
      const temp = passChars[i];
      passChars[i] = passChars[swapIndex];
      passChars[swapIndex] = temp;
    };

    // return the password character list concatenated to a string
    return passChars.join("");
  }
  // if user's response is invalid
  else {
    // alert user
    window.alert("You need to provide a valid length!");
    // return user back to their questions
    
    /* Removed for testing purposes to break the endless loop. */
    // return generatePassword();
  }
};

function getRandomChar(fromString){
  return fromString[randRange(fromString.length)];
};

// Generate a random integer r with equal chance in  0 <= r < max.
function randRange(max) {
    const requestBytes = Math.ceil(Math.log2(max) / 8);
    if (!requestBytes) { // No randomness required
        return 0;
    };
    const maxNum = Math.pow(256, requestBytes);
    const ar = new Uint8Array(requestBytes);

    while (true) {
        window.crypto.getRandomValues(ar);

        let val = 0;
        for (let i = 0; i < requestBytes;i++) {
            val = (val << 8) + ar[i];
        };

        if (val < maxNum - maxNum % max) {
            return val % max;
        };
    };
};
<button id="generate">Run</button>

与其即时进行,不如将问题与生成密码的实际函数分开。

然后简单地计算启用的选项并将该数字除以密码长度,然后就是您使用的每组字符数,然后您也可以使用该数字重复每组以平均填充总字符数需要密码

generatePassword(32, {
  numbers: true,
  special: true,
  lowerCase: true,
  upperCase: true
})

aDq.6@9l%Hx=OgS'(3WZNI?372siy12$

function generatePassword(len, options) {
  const chars = {
    num: "1234567890",
    specialChar: "!@#$%&'()*+,^-./:;<=>?[]_`{~}|",
    lowerCase: "abcdefghijklmnopqrstuvwxyz",
    upperCase: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
    custom: options.custom || undefined
  };

  const shuffleStr = str => str.split('').sort(() => 0.5 - Math.random()).join('')

  const factor = Math.ceil(len / Object.values(options).reduce((a, b) => b ? a + 1 : a, 0))

  let str = ''
  if (options.numbers) str += shuffleStr(chars.num.repeat(factor)).substr(0, factor)
  if (options.special) str += shuffleStr(chars.specialChar.repeat(factor)).substr(0, factor)
  if (options.lowerCase) str += shuffleStr(chars.lowerCase.repeat(factor)).substr(0, factor)
  if (options.upperCase) str += shuffleStr(chars.upperCase.repeat(factor)).substr(0, factor)
  if (options.custom) str += shuffleStr(chars.custom.repeat(factor)).substr(0, factor)

  return shuffleStr(str).substr(0, len)
}

console.log(generatePassword(32, {
  numbers: true,
  special: true,
  lowerCase: true,
  upperCase: true
}))

console.log(generatePassword(32, {
  numbers: true,
  special: false,
  lowerCase: false,
  upperCase: false
}))

console.log(generatePassword(32, {
  numbers: false,
  special: true,
  lowerCase: false,
  upperCase: false
}))

console.log(generatePassword(32, {
  numbers: false,
  special: false,
  lowerCase: true,
  upperCase: false
}))

console.log(generatePassword(32, {
  numbers: false,
  special: false,
  lowerCase: false,
  upperCase: true
}))

console.log(generatePassword(32, {
  custom: 'abc1'
}))