diff options
-rw-r--r-- | src/js/entropy.js | 46 | ||||
-rw-r--r-- | src/js/index.js | 52 | ||||
-rw-r--r-- | tests.js | 50 |
3 files changed, 81 insertions, 67 deletions
diff --git a/src/js/entropy.js b/src/js/entropy.js index c28620a..5900346 100644 --- a/src/js/entropy.js +++ b/src/js/entropy.js | |||
@@ -117,6 +117,40 @@ window.Entropy = new (function() { | |||
117 | while (entropyBin.length < expectedBits) { | 117 | while (entropyBin.length < expectedBits) { |
118 | entropyBin = "0" + entropyBin; | 118 | entropyBin = "0" + entropyBin; |
119 | } | 119 | } |
120 | // Assume cards are NOT replaced. | ||
121 | // Additional entropy decreases as more cards are used. This means | ||
122 | // entropy is measured using n!, not base^n. | ||
123 | // eg the second last card can be only one of two, not one of fifty two | ||
124 | // so the added entropy for that card is only one bit at most | ||
125 | if (base.asInt == 52) { | ||
126 | // Get the maximum value without replacement | ||
127 | var totalDecks = Math.ceil(base.parts.length / 52); | ||
128 | var totalCards = totalDecks * 52; | ||
129 | var totalCombos = factorial(52).pow(totalDecks); | ||
130 | var totalRemainingCards = totalCards - base.parts.length; | ||
131 | var remainingDecks = Math.floor(totalRemainingCards / 52); | ||
132 | var remainingCards = totalRemainingCards % 52; | ||
133 | var remainingCombos = factorial(52).pow(remainingDecks).multiply(factorial(remainingCards)); | ||
134 | var currentCombos = totalCombos.divide(remainingCombos); | ||
135 | var numberOfBits = Math.log2(currentCombos); | ||
136 | var maxWithoutReplace = BigInteger.pow(2, numberOfBits); | ||
137 | // aggresive flooring of numberOfBits by BigInteger.pow means a | ||
138 | // more accurate result can be had for small numbers using the | ||
139 | // built-in Math.pow function. | ||
140 | if (numberOfBits < 32) { | ||
141 | maxWithoutReplace = BigInteger(Math.round(Math.pow(2, numberOfBits))); | ||
142 | } | ||
143 | // Get the maximum value with replacement | ||
144 | var maxWithReplace = BigInteger.pow(52, base.parts.length); | ||
145 | // Calculate the new value by scaling the original value down | ||
146 | var withoutReplace = entropyInt.multiply(maxWithoutReplace).divide(maxWithReplace); | ||
147 | // Left pad with zeros based on number of bits | ||
148 | var entropyBin = withoutReplace.toString(2); | ||
149 | var numberOfBitsInt = Math.floor(numberOfBits); | ||
150 | while (entropyBin.length < numberOfBitsInt) { | ||
151 | entropyBin = "0" + entropyBin; | ||
152 | } | ||
153 | } | ||
120 | // Supply a 'filtered' entropy string for display purposes | 154 | // Supply a 'filtered' entropy string for display purposes |
121 | var entropyClean = base.parts.join(""); | 155 | var entropyClean = base.parts.join(""); |
122 | var entropyHtml = base.parts.join(""); | 156 | var entropyHtml = base.parts.join(""); |
@@ -221,4 +255,16 @@ window.Entropy = new (function() { | |||
221 | return BigInteger.log(x) / BigInteger.log(2); | 255 | return BigInteger.log(x) / BigInteger.log(2); |
222 | }; | 256 | }; |
223 | 257 | ||
258 | // Depends on BigInteger | ||
259 | function factorial(n) { | ||
260 | if (n == 0) { | ||
261 | return 1; | ||
262 | } | ||
263 | f = BigInteger.ONE; | ||
264 | for (var i=1; i<=n; i++) { | ||
265 | f = f.multiply(new BigInteger(i)); | ||
266 | } | ||
267 | return f; | ||
268 | } | ||
269 | |||
224 | })(); | 270 | })(); |
diff --git a/src/js/index.js b/src/js/index.js index f4163ee..254b62f 100644 --- a/src/js/index.js +++ b/src/js/index.js | |||
@@ -791,20 +791,21 @@ | |||
791 | } | 791 | } |
792 | 792 | ||
793 | function showEntropyFeedback(entropy) { | 793 | function showEntropyFeedback(entropy) { |
794 | var numberOfBits = entropy.binaryStr.length; | ||
794 | var strength = "extremely weak"; | 795 | var strength = "extremely weak"; |
795 | if (entropy.binaryStr.length >= 64) { | 796 | if (numberOfBits >= 64) { |
796 | strength = "very weak"; | 797 | strength = "very weak"; |
797 | } | 798 | } |
798 | if (entropy.binaryStr.length >= 96) { | 799 | if (numberOfBits >= 96) { |
799 | strength = "weak"; | 800 | strength = "weak"; |
800 | } | 801 | } |
801 | if (entropy.binaryStr.length >= 128) { | 802 | if (numberOfBits >= 128) { |
802 | strength = "strong"; | 803 | strength = "strong"; |
803 | } | 804 | } |
804 | if (entropy.binaryStr.length >= 160) { | 805 | if (numberOfBits >= 160) { |
805 | strength = "very strong"; | 806 | strength = "very strong"; |
806 | } | 807 | } |
807 | if (entropy.binaryStr.length >= 192) { | 808 | if (numberOfBits >= 192) { |
808 | strength = "extremely strong"; | 809 | strength = "extremely strong"; |
809 | } | 810 | } |
810 | // If time to crack is less than one day, and password is considered | 811 | // If time to crack is less than one day, and password is considered |
@@ -825,38 +826,17 @@ | |||
825 | console.log("Error detecting entropy strength with zxcvbn:"); | 826 | console.log("Error detecting entropy strength with zxcvbn:"); |
826 | console.log(e); | 827 | console.log(e); |
827 | } | 828 | } |
828 | var bitsStr = getNumberOfEntropyBits(entropy); | ||
829 | var wordCount = Math.floor(entropy.binaryStr.length / 32) * 3; | ||
830 | var entropyTypeStr = getEntropyTypeStr(entropy); | 829 | var entropyTypeStr = getEntropyTypeStr(entropy); |
830 | var wordCount = Math.floor(numberOfBits / 32) * 3; | ||
831 | var bitsPerEvent = Math.log2(entropy.base.asInt).toFixed(2); | ||
831 | DOM.entropyFiltered.html(entropy.cleanHtml); | 832 | DOM.entropyFiltered.html(entropy.cleanHtml); |
832 | DOM.entropyType.text(entropyTypeStr); | 833 | DOM.entropyType.text(entropyTypeStr); |
833 | DOM.entropyStrength.text(strength); | 834 | DOM.entropyStrength.text(strength); |
834 | DOM.entropyEventCount.text(entropy.base.ints.length); | 835 | DOM.entropyEventCount.text(entropy.base.ints.length); |
835 | DOM.entropyBits.text(bitsStr); | 836 | DOM.entropyBits.text(numberOfBits); |
836 | DOM.entropyWordCount.text(wordCount); | 837 | DOM.entropyWordCount.text(wordCount); |
837 | DOM.entropyBinary.text(entropy.binaryStr); | 838 | DOM.entropyBinary.text(entropy.binaryStr); |
838 | DOM.entropyBitsPerEvent.text(Math.log2(entropy.base.asInt).toFixed(2)); | 839 | DOM.entropyBitsPerEvent.text(bitsPerEvent); |
839 | } | ||
840 | |||
841 | function getNumberOfEntropyBits(entropy) { | ||
842 | var bitsStr = entropy.binaryStr.length.toString(); | ||
843 | // If using cards, assume they are not reused, thus additional entropy | ||
844 | // decreases as more cards are used. This means entropy is measured | ||
845 | // using n!, not base^n. | ||
846 | // eg the second last card can be only one of two, not one of fifty two | ||
847 | // so the added entropy for that card is only one bit at most | ||
848 | if (entropy.base.asInt == 52) { | ||
849 | var totalDecks = Math.ceil(entropy.base.parts.length / 52); | ||
850 | var totalCards = totalDecks * 52; | ||
851 | var totalCombos = factorial(52).pow(totalDecks); | ||
852 | var totalRemainingCards = totalCards - entropy.base.parts.length; | ||
853 | var remainingDecks = Math.floor(totalRemainingCards / 52); | ||
854 | var remainingCards = totalRemainingCards % 52; | ||
855 | var remainingCombos = factorial(52).pow(remainingDecks) * factorial(remainingCards); | ||
856 | var currentCombos = totalCombos.divide(remainingCombos); | ||
857 | bitsStr = currentCombos.toString(2).length.toString(); | ||
858 | } | ||
859 | return bitsStr | ||
860 | } | 840 | } |
861 | 841 | ||
862 | function getEntropyTypeStr(entropy) { | 842 | function getEntropyTypeStr(entropy) { |
@@ -922,18 +902,6 @@ | |||
922 | return typeStr; | 902 | return typeStr; |
923 | } | 903 | } |
924 | 904 | ||
925 | // Depends on BigInteger | ||
926 | function factorial(n) { | ||
927 | if (n == 0) { | ||
928 | return 1; | ||
929 | } | ||
930 | f = BigInteger.ONE; | ||
931 | for (var i=1; i<=n; i++) { | ||
932 | f = f.multiply(new BigInteger(i)); | ||
933 | } | ||
934 | return f; | ||
935 | } | ||
936 | |||
937 | var networks = [ | 905 | var networks = [ |
938 | { | 906 | { |
939 | name: "Bitcoin", | 907 | name: "Bitcoin", |
@@ -2185,10 +2185,9 @@ page.open(url, function(status) { | |||
2185 | try { | 2185 | try { |
2186 | var cards = [ | 2186 | var cards = [ |
2187 | [ "ac", "00000" ], | 2187 | [ "ac", "00000" ], |
2188 | [ "acac", "00000000000" ], | 2188 | [ "acqs", "00000110001" ], |
2189 | [ "acac2c", "00000000000000001" ], | 2189 | [ "acks", "00000110010" ], |
2190 | [ "acks", "00000110011" ], | 2190 | [ "2cac", "00000110011" ], |
2191 | [ "acacks", "00000000000110011" ], | ||
2192 | [ "2c", "00001" ], | 2191 | [ "2c", "00001" ], |
2193 | [ "3d", "01111" ], | 2192 | [ "3d", "01111" ], |
2194 | [ "4h", "11101" ], | 2193 | [ "4h", "11101" ], |
@@ -2201,8 +2200,8 @@ page.open(url, function(status) { | |||
2201 | [ "jd", "10111" ], | 2200 | [ "jd", "10111" ], |
2202 | [ "qh", "100101" ], | 2201 | [ "qh", "100101" ], |
2203 | [ "ks", "110011" ], | 2202 | [ "ks", "110011" ], |
2204 | [ "ks2c", "101001011101" ], | 2203 | [ "ks2c", "101000101001" ], |
2205 | [ "KS2C", "101001011101" ], | 2204 | [ "KS2C", "101000101001" ], |
2206 | ]; | 2205 | ]; |
2207 | for (var i=0; i<cards.length; i++) { | 2206 | for (var i=0; i<cards.length; i++) { |
2208 | var card = cards[i][0]; | 2207 | var card = cards[i][0]; |
@@ -2503,7 +2502,7 @@ page.open(url, function(status) { | |||
2503 | [ "222F", "16" ], | 2502 | [ "222F", "16" ], |
2504 | [ "FFFF", "16" ], | 2503 | [ "FFFF", "16" ], |
2505 | [ "0000101017", "33" ], // 10 events at 3.32 bits per event | 2504 | [ "0000101017", "33" ], // 10 events at 3.32 bits per event |
2506 | [ "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", "226" ], // cards are not replaced, so a full deck is not 52^52 entropy which is 296 bits, it's 52!, which is 226 bits | 2505 | [ "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", "225" ], // cards are not replaced, so a full deck is not 52^52 entropy which is 296 bits, it's 52!, which is 225 bits |
2507 | ] | 2506 | ] |
2508 | // use entropy | 2507 | // use entropy |
2509 | page.evaluate(function(e) { | 2508 | page.evaluate(function(e) { |
@@ -2636,41 +2635,42 @@ page.open(url, function(status) { | |||
2636 | entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", | 2635 | entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", |
2637 | type: "card (full deck)", | 2636 | type: "card (full deck)", |
2638 | events: 52, | 2637 | events: 52, |
2639 | bits: 226, | 2638 | bits: 225, |
2640 | words: 27, | 2639 | words: 21, |
2641 | strength: "extremely strong", | 2640 | strength: "extremely strong", |
2642 | }, | 2641 | }, |
2643 | { | 2642 | { |
2644 | entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks3d", | 2643 | entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks3d", |
2645 | type: "card (full deck, 1 duplicate: 3d)", | 2644 | type: "card (full deck, 1 duplicate: 3d)", |
2646 | events: 53, | 2645 | events: 53, |
2647 | bits: 232, | 2646 | bits: 231, |
2648 | words: 27, | 2647 | words: 21, |
2649 | strength: "extremely strong", | 2648 | strength: "extremely strong", |
2650 | }, | 2649 | }, |
2651 | { | 2650 | { |
2652 | entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d", | 2651 | entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d", |
2653 | type: "card (2 duplicates: 3d 4d, 1 missing: KS)", | 2652 | type: "card (2 duplicates: 3d 4d, 1 missing: KS)", |
2654 | events: 53, | 2653 | events: 53, |
2655 | bits: 232, | 2654 | bits: 231, |
2656 | words: 27, | 2655 | words: 21, |
2657 | strength: "extremely strong", | 2656 | strength: "extremely strong", |
2658 | }, | 2657 | }, |
2659 | { | 2658 | { |
2660 | entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d5d6d", | 2659 | entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqs3d4d5d6d", |
2661 | type: "card (4 duplicates: 3d 4d 5d..., 1 missing: KS)", | 2660 | type: "card (4 duplicates: 3d 4d 5d..., 1 missing: KS)", |
2662 | events: 53, | 2661 | events: 53, |
2663 | bits: 243, | 2662 | bits: 242, |
2664 | words: 27, | 2663 | words: 21, |
2665 | strength: "extremely strong", | 2664 | strength: "extremely strong", |
2666 | }, | 2665 | }, |
2667 | // Next test was throwing uncaught error in zxcvbn | 2666 | // Next test was throwing uncaught error in zxcvbn |
2667 | // Also tests 451 bits, ie Math.log2(52!)*2 = 225.58 * 2 | ||
2668 | { | 2668 | { |
2669 | entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsksac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", | 2669 | entropy: "ac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsksac2c3c4c5c6c7c8c9ctcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", |
2670 | type: "card (full deck, 52 duplicates: ac 2c 3c...)", | 2670 | type: "card (full deck, 52 duplicates: ac 2c 3c...)", |
2671 | events: 104, | 2671 | events: 104, |
2672 | bits: 452, | 2672 | bits: 451, |
2673 | words: 54, | 2673 | words: 42, |
2674 | strength: "extremely strong", | 2674 | strength: "extremely strong", |
2675 | }, | 2675 | }, |
2676 | // Case insensitivity to duplicate cards | 2676 | // Case insensitivity to duplicate cards |
@@ -2695,24 +2695,24 @@ page.open(url, function(status) { | |||
2695 | entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", | 2695 | entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d5d6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", |
2696 | type: "card (1 missing: 9C)", | 2696 | type: "card (1 missing: 9C)", |
2697 | events: 51, | 2697 | events: 51, |
2698 | bits: 226, | 2698 | bits: 225, |
2699 | words: 27, | 2699 | words: 21, |
2700 | strength: "extremely strong", | 2700 | strength: "extremely strong", |
2701 | }, | 2701 | }, |
2702 | { | 2702 | { |
2703 | entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", | 2703 | entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjdqdkdah2h3h4h5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", |
2704 | type: "card (2 missing: 9C 5D)", | 2704 | type: "card (2 missing: 9C 5D)", |
2705 | events: 50, | 2705 | events: 50, |
2706 | bits: 225, | 2706 | bits: 224, |
2707 | words: 24, | 2707 | words: 21, |
2708 | strength: "extremely strong", | 2708 | strength: "extremely strong", |
2709 | }, | 2709 | }, |
2710 | { | 2710 | { |
2711 | entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjd kdah2h3h 5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", | 2711 | entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d7d8d9dtdjd kdah2h3h 5h6h7h8h9hthjhqhkhas2s3s4s5s6s7s8s9stsjsqsks", |
2712 | type: "card (4 missing: 9C 5D QD...)", | 2712 | type: "card (4 missing: 9C 5D QD...)", |
2713 | events: 48, | 2713 | events: 48, |
2714 | bits: 221, | 2714 | bits: 220, |
2715 | words: 24, | 2715 | words: 18, |
2716 | strength: "extremely strong", | 2716 | strength: "extremely strong", |
2717 | }, | 2717 | }, |
2718 | // More than six missing cards does not show message | 2718 | // More than six missing cards does not show message |
@@ -2720,8 +2720,8 @@ page.open(url, function(status) { | |||
2720 | entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d 8d9d jd kdah2h3h 5h6h7h8h9hthjhqhkh 2s3s4s5s6s7s8s9stsjsqsks", | 2720 | entropy: "ac2c3c4c5c6c7c8c tcjcqckcad2d3d4d 6d 8d9d jd kdah2h3h 5h6h7h8h9hthjhqhkh 2s3s4s5s6s7s8s9stsjsqsks", |
2721 | type: "card", | 2721 | type: "card", |
2722 | events: 45, | 2722 | events: 45, |
2723 | bits: 214, | 2723 | bits: 213, |
2724 | words: 24, | 2724 | words: 18, |
2725 | strength: "extremely strong", | 2725 | strength: "extremely strong", |
2726 | }, | 2726 | }, |
2727 | ]; | 2727 | ]; |