【问题标题】:Is my password generating function inefficient? [closed]我的密码生成功能效率低吗? [关闭]
【发布时间】:2013-04-28 16:04:05
【问题描述】:

我有这个 javascript 密码生成功能。现在我正在丢弃不符合所选规范的密码。例如,如果密码不包含数字,我会丢弃它并生成一个新的一跳,其中包含一个数字。然而,这似乎不是有效的性能虎钳,至少对我来说不是。

有没有更好的方法来实现生成密码中特定字符的强制?

我还计划添加,以便可以强制密码包含特殊字符。如果我以当前的方式执行此操作,我将不得不使用一些正则表达式来检查密码是否包含特殊字符,如果不包含特殊字符,则将其丢弃(再次对我来说似乎不是很有效)。

function generatePassword(length, charset, nosimilar) {
    // default parameters
    length = (typeof length === "undefined") ? 8 : length;
    charset = (typeof charset === "undefined") ? 'abcdefghjknpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ123456789' : charset;
    nosimilar = (typeof similar === "undefined") ? true : nosimilar;

    var gen;
    retVal = "";
    for (var i = 0, n = charset.length; i < length; ++i) {
        gen = charset.charAt(Math.floor(Math.random() * n))
        if ( (retVal.charAt( retVal.length-1 ) == gen) && (nosimilar)) {
            retVal = retVal.substring(0, retVal.length - 1)
            retVal += charset.charAt(Math.floor(Math.random() * n))
            console.log('Generated character same as the last one. Trunkated and regenerated.');
        }
        retVal += gen;
    }

    // if charset contains numbers make sure we get atleast one number
    if ( (retVal.match(/\d+/g) == null) && (charset.match(/\d+/g) != null)) {
    console.log('Password generated but no numbers found. Regenerating.');
    generatePassword(length, charset, nosimilar);
    }

    return retVal;
}

if ($("#chLetters").prop('checked')) charset += 'abcdefghjknpqrstuvwxyz';
if ($("#chNumbers").prop('checked')) charset += '123456789';
if ($("#chMixedCase").prop('checked')) charset += 'ABCDEFGHJKLMNPQRSTUVWXYZ';
if ($("#chSpecial").prop('checked')) charset += '!@$%&?+*-_';

$("#passgen").text(generatePassword($("#maxLength").val(), charset, $("#chNoSimilar").prop('checked')));

【问题讨论】:

  • 您想要的密码可以是完全随机的吗?我知道必须有特殊标志,但还有其他规则吗?特殊符号应该出现在字符串的什么位置?
  • 好吧,我会在不同的步骤中使用不同的字符集。示例:小写集、大写集、数字集、特殊集。然后从每个集合中选择元素开始,将它们放在字符串中的随机位置。例如,4 个小写元素,2 个大写字母,2 个数字,1 个特殊元素。
  • 这个问题在codereview.stackexchange.com会更好吗?
  • 这个想法是将不同的字符集传递给函数,具体取决于密码的外观。如果这个问题更多地属于 codereview,我也存在分歧。不知道,随意移动它。
  • 我认为如果主题发生变化,这将更适合这里:例如“如何确保生成的密码符合要求”就可以了。 (尤其是因为关于“效率低下”的问题有点学术性:即使该函数在找到符合规范的密码之前运行一千次,最终用户也不会注意到速度上有任何差异。)

标签: javascript function random passwords


【解决方案1】:

如果您的密码长度为 n 个字符,并且要求它至少包含一个字母、一个数字和一个特殊字符,则意味着每个密码将包含 1 到 n-2 个字符。例如(简化):

function generatePassword( length ) {
    var letters = 'abcdefghjknpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ',
        special = '{}()#%&',
        characters = [],
        amountOfLetters = Math.floor( Math.random() * ( length - 2 ) ) + 1,
        amountOfNumbers = Math.floor( Math.random() * ( length - 1 - amountOfLetters ) ) + 1,
        amountOfSpecial = length - ( amountOfLetters + amountOfNumbers );

    // pick letters
    for (var i = 0, n = letters.length; i < amountOfLetters; ++i) {
        characters.push( letters.charAt( Math.floor( Math.random() * n ) ) );
    }

    // pick numbers
    for ( i = 0; i < amountOfNumbers; ++i) {
        characters.push( ''+( Math.floor( Math.random() * 9 ) + 1 ) );
    }

    // pick special characters
    for ( i = 0, n = special.length; i < amountOfSpecial; ++i) {
        characters.push( special.charAt( Math.floor( Math.random() * n ) ) );
    }

    // sort the array and concatenate elements into a string
    return characters.sort( function( a, b ) {
        return Math.random() - 0.5;
    } ).join( '' );
}

演示:http://jsfiddle.net/gGwyM/

该函数选择 1 到 n-2 个字母,然后是 1 到 n-L-1 个数字(其中 L 是字母的数量),其余的是特殊字符。这保证了密码至少包含每个组中的一个字符。

(请注意,您应该使用比我这里更好的函数来随机化数组,参见例如How to randomize (shuffle) a JavaScript array?

【讨论】:

  • 为什么您的数字生成器不选择任何零?
  • 因为 OP 在原始字符集中没有零。我认为这是因为它很容易与大写 O 混淆。(我个人也会跳过 1,因为它看起来像小写 L 或大写 I。)
【解决方案2】:

你可以使用这样的东西。使用这种方法,您可以创建多个密钥集,并且通过使用简单的选项属性,您可以在密码生成中切换这些密钥集,以及设置长度甚至选择生成十六进制密码。

function isObject(value) {
  return Object.prototype.toString.call(value) === '[object Object]';
}

function assign(target, source) {
  for (var prop in source) {
    if (source.hasOwnProperty(prop)) {
      target[prop] = source[prop];
    }
  }

  return target;
}

function shuffle(obj) {
  var i = obj.length;
  var rnd, tmp;

  while (i) {
    rnd = Math.floor(Math.random() * i);
    i -= 1;
    tmp = obj[i];
    obj[i] = obj[rnd];
    obj[rnd] = tmp;
  }

  return obj;
}

function generatePassword(options) {
  var opts = isObject(options) ? assign({}, options) : {};
  var keyspace = '';
  if (opts.hex) {
    keyspace = '0123456789abcdef';
    if (opts.uppercase) {
      keyspace = keyspace.toUpperCase();
    }
  } else {
    if (opts.alpha) {
      keyspace += 'abcdefghijklmnopqrstuvwxyz';
    }

    if (opts.uppercase) {
      keyspace += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    }

    if (opts.numeric) {
      keyspace += '0123456789';
    }

    if (opts.punctuation) {
      keyspace += "`!\"?$?%^&*()_-+={[}]:;@'~#|\\<,>.?/";
    }
  }

  if (keyspace.length - 1 < 0) {
    return '';
  }

  opts.size = opts.size >>> 0 || 16;
  if (opts.size < 5) {
    opts.size = 5;
  } else if (opts.size > 100) {
    opts.size = 100;
  }

  return shuffle(keyspace.split('')).join('').slice(0, opts.size);
}

var password = generatePassword({
  alpha: true,
  uppercase: true,
  numeric: true,
  punctuation: true
});

console.log(password);

其他选项是十六进制生成长度的大小

您可以修改它以获取您想要使用的特殊字符的字符串。

更新:这是一个更复杂的示例,它强制执行每种类型的所选字符的数量,并且与上面的简单示例类似。

function isObject(value) {
  return Object.prototype.toString.call(value) === '[object Object]';
}

function assign(target, source) {
  for (var prop in source) {
    if (source.hasOwnProperty(prop)) {
      target[prop] = source[prop];
    }
  }

  return target;
}

function shuffle(obj) {
  var i = obj.length;
  var rnd, tmp;

  while (i) {
    rnd = Math.floor(Math.random() * i);
    i -= 1;
    tmp = obj[i];
    obj[i] = obj[rnd];
    obj[rnd] = tmp;
  }

  return obj;
}

function getXChars(string, number) {
  var str = typeof string === 'string' ? string : '';
  var num = typeof number === 'number' && number > 0 ? number : 0;
  var array = [];
  var i = str.length;
  var rnd;

  while (i && array.length < num) {
    rnd = Math.floor(Math.random() * i);
    i -= 1;
    array.push(str.charAt(rnd));
  }

  return array;
}

function generatePassword(opts) {
  var opts = isObject(opts) ? assign({}, opts) : {};
  var keyspace = '';
  var result = [];
  var i = 0;
  var tmp;

  if (typeof opts.hex === 'number' && opts.hex > 0) {
    i += opts.hex;
    keyspace = '0123456789abcdef';
    if (opts.uppercase === true) {
      keyspace = keyspace.toUpperCase();
    }

    result = result.concat(getXChars(keyspace, opts.hex));
  } else {
    if (typeof opts.alpha === 'number' && opts.alpha > 0) {
      i += opts.alpha;
      tmp = 'abcdefghijklmnopqrstuvwxyz';
      keyspace += tmp;
      result = result.concat(getXChars(tmp, opts.alpha));
    }

    if (typeof opts.uppercase === 'number' && opts.uppercase > 0) {
      i += opts.uppercase;
      tmp = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
      keyspace += tmp;
      result = result.concat(getXChars(tmp, opts.uppercase));
    }

    if (typeof opts.numeric === 'number' && opts.numeric > 0) {
      i += opts.numeric;
      tmp = '0123456789';
      keyspace += tmp;
      result = result.concat(getXChars(tmp, opts.numeric));
    }

    if (typeof opts.punctuation === 'number' && opts.punctuation > 0) {
      i += opts.punctuation;
      tmp = "`!\"?$?%^&*()_-+={[}]:;@'~#|\\<,>.?/";
      keyspace += tmp;
      result = result.concat(getXChars(tmp, opts.punctuation));
    }
  }

  if (keyspace.length === 0) {
    return keyspace;
  }

  opts.size = opts.size >>> 0 || 16;
  if (opts.size < 5) {
    opts.size = 5;
  } else if (opts.size > 100) {
    opts.size = 100;
  }

  result = result.concat(getXChars(keyspace, opts.size - i));

  return shuffle(result).join('');
}

var password = generatePassword({
  alpha: 1,
  uppercase: 1,
  numeric: 1,
  punctuation: 1
});

console.log(password);

更新:正如这里所承诺的那样,Math.random 的可能替代品。如果window.crypto.getRandomValues 可用,那么它将使用它,否则它会回退到 Math.random。

function random() {
  if (window.crypto && typeof window.crypto.getRandomValues === 'function') {
    console.log('Using crypto');
    var array = new Uint32Array(1);
    window.crypto.getRandomValues(array);
    return array[0] / (Math.pow(2, 32) + 1);
  }

  console.log('Using random');
  return Math.random();
}

console.log(random());

【讨论】:

  • 想解释否决票?
  • 不是我的反对意见,而是:缺少为什么你的更好的解释,你不应该设置l=keyspace.length-1 - 这意味着你永远不会生成最后一个密钥。
  • 我们使用的是 Math.round 而不是 Math.floor 所以应该没问题
  • 呃,没看到。尽管如此,生成的密钥仍然不是均匀分布的。
  • 不,随机不是一种完全安全且分布均匀的密码生成方法。最好使用“crypto.getRandomValues”,但目前跨浏览器的支持非常薄弱。 @Juhana 的解决方案通过将随机值加 1 来解决同样的问题。
【解决方案3】:

我认为您需要根据数组的要求从不同的数组中挑选字符。这是我的解决方案:

var alphas = "abcdefghjknpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ";
var specials = "!#$%&/()=`";
var numbers = "1234567890";

var requiredSpecials = 1;
var requiredNumbers = 2;

function pickRandom(str, count){
    var remaining = str;
    var result = "";
    while (remaining.length > 0 && count > 0){
        // pick random char from remaining
        var char = remaining.charAt(Math.floor(Math.random() *remaining.length));

        // remove char from remaining. (Just replace with empty string)
        remaining = remaining.replace(char, "");

        // add char to result
        result += char;

        // decrement count
        count -= 1;
    }

    return result;
}

function shuffleString(str){
    var arr = str.split(''); // Convert to array
    arr.sort(function(){ // Sort by random
        return 0.5-Math.random()
    })
    return arr.join(''); // Join back to string
}

function generate(length){
    var specialCount = requiredSpecials;
    var numberCount = requiredNumbers;
    var alphaCount = length - specialCount - numberCount;

    var tmp = pickRandom(alphas, alphaCount) + pickRandom(specials, specialCount) + pickRandom(numbers, numberCount);

    // Shuffle and return
    return shuffleString(tmp);
}

alert(generate(9))

你也可以在jsFiddle玩它

【讨论】:

    猜你喜欢
    • 2013-07-15
    • 2017-02-03
    • 2012-08-11
    • 2021-09-22
    • 1970-01-01
    • 1970-01-01
    • 2013-09-18
    • 2016-02-06
    • 2011-10-01
    相关资源
    最近更新 更多