├── .editorconfig ├── .eslintrc.js ├── .gitattributes ├── .github ├── renovate.json └── workflows │ ├── nodejs.yml │ └── release.yml ├── .gitignore ├── .npmrc ├── .releaserc.json ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── bin └── cmd.js ├── index.d.ts ├── index.js ├── lib ├── crypt.js ├── html-escape.js └── sjisconv.js ├── package.json └── test ├── fixtures └── tripcodes.txt ├── generate-trip-list.js └── index.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 2 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: '@kenan', 3 | rules: { 4 | 'no-var': 0 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "local>kenany/renovate-config", 5 | ":assignAndReview(kenany)" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: nodejs 2 | on: [push, pull_request, merge_group] 3 | permissions: 4 | contents: read 5 | jobs: 6 | build: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | node-version: [18, 20] 11 | os: [ubuntu-latest] 12 | steps: 13 | - name: Node.js ${{ matrix.node-version }} 14 | uses: actions/setup-node@v4.4.0 15 | with: 16 | node-version: ${{ matrix.node-version }} 17 | - name: Update npm 18 | run: | 19 | npm install -g npm 20 | npm --version 21 | - uses: actions/checkout@v4.2.2 22 | - name: Install dependencies 23 | uses: bahmutov/npm-install@v1.10.9 24 | with: 25 | useLockFile: false 26 | - run: npm ls 27 | - name: Test 28 | run: npm test 29 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | push: 4 | branches: 5 | - master 6 | permissions: 7 | contents: read 8 | jobs: 9 | release: 10 | name: release 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: write 14 | id-token: write 15 | issues: write 16 | pull-requests: write 17 | steps: 18 | - name: Set up Node.js 19 | uses: actions/setup-node@v4.4.0 20 | with: 21 | node-version: 20 22 | - name: Update npm 23 | run: | 24 | npm install -g npm 25 | npm --version 26 | - name: Checkout code 27 | uses: actions/checkout@v4.2.2 28 | - name: Install dependencies 29 | uses: bahmutov/npm-install@v1.10.9 30 | with: 31 | useLockFile: false 32 | - run: npm audit signatures 33 | - name: Release 34 | run: npm run release 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock = false 2 | provenance = true 3 | -------------------------------------------------------------------------------- /.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "@semantic-release/commit-analyzer", 4 | "@semantic-release/release-notes-generator", 5 | "@semantic-release/changelog", 6 | "@semantic-release/github", 7 | "@semantic-release/npm", 8 | "@semantic-release/git" 9 | ], 10 | "preset": "conventionalcommits", 11 | "tagFormat": "${version}" 12 | } 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [4.0.0](https://github.com/kenany/tripcode/compare/3.0.3...4.0.0) (2023-09-20) 2 | 3 | 4 | ### ⚠ BREAKING CHANGES 5 | 6 | * Node.js v14 and v16 are no longer supported. 7 | 8 | ### Features 9 | 10 | * drop Node.js v14 and v16 support ([4db365f](https://github.com/kenany/tripcode/commit/4db365f0b2f2808b31732085556af273b16e6d10)) 11 | 12 | ## [3.0.3](https://github.com/KenanY/tripcode/compare/3.0.2...3.0.3) (2023-03-26) 13 | 14 | 15 | ### Bug Fixes 16 | 17 | * include TS types in published package ([b014f46](https://github.com/KenanY/tripcode/commit/b014f46f55ff5b8496bc7ffc0017b78f0f28925e)) 18 | 19 | ## [3.0.2](https://github.com/KenanY/tripcode/compare/3.0.1...3.0.2) (2023-02-18) 20 | 21 | 22 | ### Bug Fixes 23 | 24 | * **deps:** minimist@1.2.8 ([17cb36b](https://github.com/KenanY/tripcode/commit/17cb36b274b8e1b6a205f4645857d357b0212e74)) 25 | 26 | ## [3.0.1](https://github.com/KenanY/tripcode/compare/3.0.0...3.0.1) (2022-11-23) 27 | 28 | 29 | ### Bug Fixes 30 | 31 | * **deps:** minimist@1.2.7 ([70aa4c5](https://github.com/KenanY/tripcode/commit/70aa4c5b539616316038cd10665d9e3d87bc243f)) 32 | 33 | ## [3.0.0](https://github.com/KenanY/tripcode/compare/2.0.1...3.0.0) (2022-11-23) 34 | 35 | 36 | ### ⚠ BREAKING CHANGES 37 | 38 | * Node.js v12 is no longer supported. 39 | 40 | ### Features 41 | 42 | * drop Node.js v12 support ([0f70e98](https://github.com/KenanY/tripcode/commit/0f70e98401aa8e0f9a8f12b59527fcc02c7d1701)) 43 | 44 | ### [2.0.1](https://github.com/KenanY/tripcode/compare/2.0.0...2.0.1) (2022-03-27) 45 | 46 | 47 | ### Bug Fixes 48 | 49 | * **deps:** minimist@1.2.6 ([758cbef](https://github.com/KenanY/tripcode/commit/758cbef58fea1625c05634b93ca7b792cc618f0f)) 50 | 51 | ## [2.0.0](https://github.com/KenanY/tripcode/compare/1.4.0...2.0.0) (2021-11-05) 52 | 53 | 54 | ### ⚠ BREAKING CHANGES 55 | 56 | * **ci:** Node.js v10 is no longer supported. 57 | 58 | ### Features 59 | 60 | * **ci:** drop Node.js v10 support ([29b8b50](https://github.com/KenanY/tripcode/commit/29b8b50b8ad7082d27180a73b7b3b26d8a963a5d)) 61 | 62 | ## [1.4.0](https://github.com/KenanY/tripcode/compare/1.3.5...1.4.0) (2021-07-24) 63 | 64 | 65 | ### Features 66 | 67 | * add typescript definitions ([66b0b87](https://github.com/KenanY/tripcode/commit/66b0b871d1a4d67cd7488a642292c772965ae802)) 68 | 69 | ### [1.3.5](https://github.com/KenanY/tripcode/compare/1.3.4...1.3.5) (2021-05-10) 70 | 71 | 72 | ### Bug Fixes 73 | 74 | * fix incorrect trip for halfwidth katakana keys ([d625b01](https://github.com/KenanY/tripcode/commit/d625b01ab08327d341aacb020e04009a28a8907a)) 75 | * **deps:** lodash.foreach@4.5.0 ([8c99d7b](https://github.com/KenanY/tripcode/commit/8c99d7bc46fe7adbd60c4a0ea58c0cd237f7fc33)) 76 | * **deps:** minimist@1.2.5 ([f5e7a9c](https://github.com/KenanY/tripcode/commit/f5e7a9c9d03c7065db79d0bbfb3421ee37d65cf7)) 77 | * **deps:** split@1.0.1 ([e462784](https://github.com/KenanY/tripcode/commit/e4627845d560dbc9525221e182e07608e7dadbfa)) 78 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2013–2023 Kenan Yildirim 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | === 21 | 22 | - unix-crypt-td-js, located at lib/crypt.js. unix-crypt-td-js's license follows: 23 | """ 24 | Copyright(C) Tim Joseph F. Dumol 2011. All rights reserved. 25 | Derived from crypt.c in the Seventh Edition Unix distribution by 26 | Caldera International, which is Copyright(C) Caldera International 27 | Inc. 2001-2002. All rights reserved. 28 | 29 | Redistribution and use in source and binary forms, 30 | with or without modification, are permitted provided that the 31 | following conditions are met: 32 | 33 | Redistributions of source code and documentation must retain the above 34 | copyright notice, this list of conditions and the following 35 | disclaimer. 36 | 37 | * Redistributions in binary form must reproduce the above copyright 38 | notice, this list of conditions and the following disclaimer in the 39 | documentation and/or other materials provided with the distribution. 40 | 41 | * All advertising materials mentioning features or use of this software 42 | must display the following acknowledgement: This product includes 43 | software developed or owned by Caldera International, Inc. 44 | 45 | * Neither the name of Caldera International, Inc. nor the names of 46 | other contributors may be used to endorse or promote products derived 47 | from this software without specific prior written permission. 48 | 49 | USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA 50 | INTERNATIONAL, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR 51 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 52 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 53 | DISCLAIMED. IN NO EVENT SHALL CALDERA INTERNATIONAL, INC. BE LIABLE 54 | FOR ANY DIRECT, INDIRECT INCIDENTAL, SPECIAL, EXEMPLARY, OR 55 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 56 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 57 | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 58 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 59 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 60 | IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 61 | """ 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tripcode 2 | 3 | JavaScript implementation of 4chan's tripcode algorithm. 4 | 5 | ## Example 6 | 7 | ``` javascript 8 | var tripcode = require('tripcode'); 9 | 10 | tripcode('f}EAmbA%'); 11 | // => '/izs/14Iuw' 12 | ``` 13 | 14 | There's even a `tripcode` command if you install globally! 15 | 16 | ``` bash 17 | $ tripcode github is cool 18 | #github => !lLf/rxkwgg 19 | #is => !4CEimo5sKs 20 | #cool => !QkO1sgFXdY 21 | ``` 22 | 23 | You can also pipe in a newline-delimited file: 24 | 25 | ``` bash 26 | $ cat > codes.txt 27 | github 28 | is 29 | cool 30 | 31 | $ tripcode < codes.txt 32 | #github => !lLf/rxkwgg 33 | #is => !4CEimo5sKs 34 | #cool => !QkO1sgFXdY 35 | ``` 36 | 37 | Or pipe out the tripcodes to `grep` or something to find specific tripcodes! 38 | 39 | ``` bash 40 | $ tripcode < /usr/share/dict/words | grep -E '(/AhWyw3toI)' 41 | #incognito => !/AhWyw3toI 42 | ``` 43 | 44 | ## Installation 45 | 46 | ``` bash 47 | $ npm install tripcode 48 | ``` 49 | 50 | Unless you want the `tripcode` command, in which case you gotta install 51 | globally: 52 | 53 | ``` bash 54 | $ npm install -g tripcode 55 | ``` 56 | 57 | ## API 58 | 59 | ``` javascript 60 | var tripcode = require('tripcode'); 61 | ``` 62 | 63 | ### tripcode(password) 64 | 65 | Returns the tripcode generated from _String_ `password`. 66 | -------------------------------------------------------------------------------- /bin/cmd.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const argv = require('minimist')(process.argv.slice(2)); 4 | const split = require('split'); 5 | 6 | const tripcode = require('../'); 7 | 8 | function tripify(value) { 9 | process.stdout.write('#' + value + ' => !' + tripcode(value) + '\n'); 10 | } 11 | 12 | // Something is being piped in. 13 | if (!process.stdin.isTTY) { 14 | // The stdin stream is paused by default. 15 | process.stdin.resume(); 16 | process.stdin.setEncoding('utf8'); 17 | process.stdin.pipe(split()).on('data', tripify); 18 | } 19 | 20 | // Password(s) passed as argument(s). 21 | else { 22 | argv._.forEach(tripify); 23 | } 24 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'tripcode' { 2 | function tripcode(key: string): string; 3 | export = tripcode; 4 | } 5 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var crypt = require('./lib/crypt'); 2 | var htmlEscape = require('./lib/html-escape'); 3 | var sjisconv = require('./lib/sjisconv'); 4 | 5 | var SALT_TABLE = '.............................................../0123456789A' 6 | + 'BCDEFGABCDEFGHIJKLMNOPQRSTUVWXYZabcdefabcdefghijklmnopqrstuvwxyz..........' 7 | + '..........................................................................' 8 | + '.................................................'; 9 | 10 | /** 11 | * @param {string} str 12 | * @returns {string} 13 | */ 14 | function sjis(str) { 15 | var encoded = ''; 16 | var index = -1; 17 | var length = str.length; 18 | while (++index < length) { 19 | var character = sjisconv[str.charAt(index)]; 20 | if (character) encoded += character; 21 | } 22 | return encoded; 23 | } 24 | 25 | /** 26 | * @param {string} key 27 | * @returns {string} 28 | */ 29 | module.exports = function(key) { 30 | key = sjis(key); 31 | key = htmlEscape(key); 32 | 33 | if (!key.length) return ''; 34 | 35 | var salt = ''; 36 | var index = 0; 37 | while (index++ < 2) { 38 | salt += SALT_TABLE[(key + 'H.').charCodeAt(index) % 256]; 39 | } 40 | 41 | return crypt(key, salt).substring(3); 42 | }; 43 | -------------------------------------------------------------------------------- /lib/crypt.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Unix crypt(3) Javascript Implementation 3 | * 4 | * Straightforward implementation of the DES-based Unix crypt(3) hash, based 5 | * largely on crypt.c in the Seventh Edition Unix distribution released by 6 | * Caldera Systems under a BSD-style license. 7 | * 8 | * @author Tim Joseph Dumol 9 | * 10 | * Modified by Kenan Yildirim in order to add CommonJS support. 11 | */ 12 | 13 | /* 14 | Legalese: 15 | 16 | Copyright(C) Tim Joseph F. Dumol 2011. All rights reserved. 17 | Derived from crypt.c in the Seventh Edition Unix distribution by 18 | Caldera International, which is Copyright(C) Caldera International 19 | Inc. 2001-2002. All rights reserved. 20 | 21 | Redistribution and use in source and binary forms, 22 | with or without modification, are permitted provided that the 23 | following conditions are met: 24 | 25 | Redistributions of source code and documentation must retain the above 26 | copyright notice, this list of conditions and the following 27 | disclaimer. 28 | 29 | * Redistributions in binary form must reproduce the above copyright 30 | notice, this list of conditions and the following disclaimer in the 31 | documentation and/or other materials provided with the distribution. 32 | 33 | * All advertising materials mentioning features or use of this software 34 | must display the following acknowledgement: This product includes 35 | software developed or owned by Caldera International, Inc. 36 | 37 | * Neither the name of Caldera International, Inc. nor the names of 38 | other contributors may be used to endorse or promote products derived 39 | from this software without specific prior written permission. 40 | 41 | USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA 42 | INTERNATIONAL, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR 43 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 44 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 45 | DISCLAIMED. IN NO EVENT SHALL CALDERA INTERNATIONAL, INC. BE LIABLE 46 | FOR ANY DIRECT, INDIRECT INCIDENTAL, SPECIAL, EXEMPLARY, OR 47 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 48 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 49 | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 50 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 51 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 52 | IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ 53 | 54 | /* 55 | * Initial permutation, 56 | */ 57 | var IP = [ 58 | 58, 50, 42, 34, 26, 18, 10, 2, 59 | 60, 52, 44, 36, 28, 20, 12, 4, 60 | 62, 54, 46, 38, 30, 22, 14, 6, 61 | 64, 56, 48, 40, 32, 24, 16, 8, 62 | 57, 49, 41, 33, 25, 17, 9, 1, 63 | 59, 51, 43, 35, 27, 19, 11, 3, 64 | 61, 53, 45, 37, 29, 21, 13, 5, 65 | 63, 55, 47, 39, 31, 23, 15, 7 66 | ]; 67 | 68 | /* 69 | * Final permutation, FP = IP^(-1) 70 | */ 71 | var FP = [ 72 | 40, 8, 48, 16, 56, 24, 64, 32, 73 | 39, 7, 47, 15, 55, 23, 63, 31, 74 | 38, 6, 46, 14, 54, 22, 62, 30, 75 | 37, 5, 45, 13, 53, 21, 61, 29, 76 | 36, 4, 44, 12, 52, 20, 60, 28, 77 | 35, 3, 43, 11, 51, 19, 59, 27, 78 | 34, 2, 42, 10, 50, 18, 58, 26, 79 | 33, 1, 41, 9, 49, 17, 57, 25 80 | ]; 81 | 82 | /* 83 | * Permuted-choice 1 from the key bits 84 | * to yield C and D. 85 | * Note that bits 8,16... are left out: 86 | * They are intended for a parity check. 87 | */ 88 | var PC1_C = [ 89 | 57, 49, 41, 33, 25, 17, 9, 90 | 1, 58, 50, 42, 34, 26, 18, 91 | 10, 2, 59, 51, 43, 35, 27, 92 | 19, 11, 3, 60, 52, 44, 36 93 | ]; 94 | 95 | var PC1_D = [ 96 | 63, 55, 47, 39, 31, 23, 15, 97 | 7, 62, 54, 46, 38, 30, 22, 98 | 14, 6, 61, 53, 45, 37, 29, 99 | 21, 13, 5, 28, 20, 12, 4 100 | ]; 101 | 102 | /* 103 | * Sequence of shifts used for the key schedule. 104 | */ 105 | var shifts = [ 106 | 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 107 | ]; 108 | 109 | /* 110 | * Permuted-choice 2, to pick out the bits from 111 | * the CD array that generate the key schedule. 112 | */ 113 | var PC2_C = [ 114 | 14, 17, 11, 24, 1, 5, 115 | 3, 28, 15, 6, 21, 10, 116 | 23, 19, 12, 4, 26, 8, 117 | 16, 7, 27, 20, 13, 2 118 | ]; 119 | 120 | var PC2_D = [ 121 | 41, 52, 31, 37, 47, 55, 122 | 30, 40, 51, 45, 33, 48, 123 | 44, 49, 39, 56, 34, 53, 124 | 46, 42, 50, 36, 29, 32 125 | ]; 126 | 127 | /* 128 | * The C and D arrays used to calculate the key schedule. 129 | */ 130 | var C = []; 131 | var D = []; 132 | 133 | /* 134 | * The key schedule. 135 | * Generated from the key. 136 | */ 137 | var KS = []; 138 | for (var i = 0; i < 16; ++i) { 139 | KS[i] = []; 140 | } 141 | 142 | /* 143 | * Set up the key schedule from the key. 144 | */ 145 | function setkey(key) { 146 | var i, j, k, t; 147 | 148 | /* 149 | * First, generate C and D by permuting 150 | * the key. The low order bit of each 151 | * 8-bit char is not used, so C and D are only 28 152 | * bits apiece. 153 | */ 154 | for (i = 0; i < 28; i++) { 155 | C[i] = key[PC1_C[i] - 1]; 156 | D[i] = key[PC1_D[i] - 1]; 157 | } 158 | /* 159 | * To generate Ki, rotate C and D according 160 | * to schedule and pick up a permutation 161 | * using PC2. 162 | */ 163 | for (i = 0; i < 16; i++) { 164 | /* 165 | * rotate. 166 | */ 167 | for (k = 0; k < shifts[i]; k++) { 168 | t = C[0]; 169 | for (j = 0; j < 28 - 1; j++) { C[j] = C[j + 1]; } 170 | C[27] = t; 171 | t = D[0]; 172 | for (j = 0; j < 28 - 1; j++) { D[j] = D[j + 1]; } 173 | D[27] = t; 174 | } 175 | /* 176 | * get Ki. Note C and D are concatenated. 177 | */ 178 | for (j = 0; j < 24; j++) { 179 | KS[i][j] = C[PC2_C[j] - 1]; 180 | KS[i][j + 24] = D[PC2_D[j] - 28 - 1]; 181 | } 182 | } 183 | } 184 | 185 | /* 186 | * The E bit-selection table. 187 | */ 188 | var E = []; 189 | var e = [ 190 | 32, 1, 2, 3, 4, 5, 191 | 4, 5, 6, 7, 8, 9, 192 | 8, 9, 10, 11, 12, 13, 193 | 12, 13, 14, 15, 16, 17, 194 | 16, 17, 18, 19, 20, 21, 195 | 20, 21, 22, 23, 24, 25, 196 | 24, 25, 26, 27, 28, 29, 197 | 28, 29, 30, 31, 32, 1 198 | ]; 199 | 200 | /* 201 | * The 8 selection functions. 202 | * For some reason, they give a 0-origin 203 | * index, unlike everything else. 204 | */ 205 | var S = [ 206 | [ 207 | 14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7, 208 | 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8, 209 | 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0, 210 | 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13 211 | ], 212 | 213 | [ 214 | 15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10, 215 | 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5, 216 | 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15, 217 | 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9 218 | ], 219 | 220 | [ 221 | 10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8, 222 | 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1, 223 | 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7, 224 | 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12 225 | ], 226 | 227 | [ 228 | 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15, 229 | 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9, 230 | 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4, 231 | 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14 232 | ], 233 | 234 | [ 235 | 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9, 236 | 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6, 237 | 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14, 238 | 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3 239 | ], 240 | 241 | [ 242 | 12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11, 243 | 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8, 244 | 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6, 245 | 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13 246 | ], 247 | 248 | [ 249 | 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1, 250 | 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6, 251 | 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2, 252 | 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12 253 | ], 254 | 255 | [ 256 | 13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7, 257 | 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2, 258 | 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8, 259 | 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11 260 | ] 261 | ]; 262 | 263 | /* 264 | * P is a permutation on the selected combination 265 | * of the current L and key. 266 | */ 267 | var P = [ 268 | 16, 7, 20, 21, 269 | 29, 12, 28, 17, 270 | 1, 15, 23, 26, 271 | 5, 18, 31, 10, 272 | 2, 8, 24, 14, 273 | 32, 27, 3, 9, 274 | 19, 13, 30, 6, 275 | 22, 11, 4, 25 276 | ]; 277 | 278 | /* 279 | * The current block, divided into 2 halves. 280 | */ 281 | var L = []; 282 | var R = []; 283 | var tempL = [ 284 | [] 285 | ]; 286 | var f = []; 287 | 288 | /* 289 | * The combination of the key and the input, before selection. 290 | */ 291 | var preS = []; 292 | 293 | /* 294 | * The payoff: encrypt a block. 295 | */ 296 | function encrypt(block, edflag) { 297 | var i, ii, j, k, t; 298 | 299 | /* 300 | * First, permute the bits in the input 301 | */ 302 | var perm = []; 303 | for (j = 0; j < 64; j++) { 304 | perm[j] = block[IP[j] - 1]; 305 | } 306 | for (j = 0; j < 32; ++j) { 307 | L[j] = perm[j]; 308 | R[j] = perm[j + 32]; 309 | } 310 | /* 311 | * Perform an encryption operation 16 times. 312 | */ 313 | for (ii = 0; ii < 16; ii++) { 314 | /* 315 | * Set direction 316 | */ 317 | if (edflag) { i = 15 - ii; } 318 | else { i = ii; } 319 | /* 320 | * Save the R array, 321 | * which will be the new L. 322 | */ 323 | for (j = 0; j < 32; j++) { tempL[j] = R[j]; } 324 | /* 325 | * Expand R to 48 bits using the E selector; 326 | * exclusive-or with the current key bits. 327 | */ 328 | for (j = 0; j < 48; j++) { preS[j] = R[E[j] - 1] ^ KS[i][j]; } 329 | /* 330 | * The pre-select bits are now considered 331 | * in 8 groups of 6 bits each. 332 | * The 8 selection functions map these 333 | * 6-bit quantities into 4-bit quantities 334 | * and the results permuted 335 | * to make an f(R, K). 336 | * The indexing into the selection functions 337 | * is peculiar; it could be simplified by 338 | * rewriting the tables. 339 | */ 340 | for (j = 0; j < 8; j++) { 341 | t = 6 * j; 342 | 343 | k = S[j][(preS[t + 0] << 5) 344 | + (preS[t + 1] << 3) 345 | + (preS[t + 2] << 2) 346 | + (preS[t + 3] << 1) 347 | + (preS[t + 4] << 0) 348 | + (preS[t + 5] << 4) 349 | ]; 350 | t = 4 * j; 351 | f[t + 0] = (k >> 3) & 1; 352 | f[t + 1] = (k >> 2) & 1; 353 | f[t + 2] = (k >> 1) & 1; 354 | f[t + 3] = (k >> 0) & 1; 355 | } 356 | /* 357 | * The new R is L ^ f(R, K). 358 | * The f here has to be permuted first, though. 359 | */ 360 | for (j = 0; j < 32; j++) { R[j] = L[j] ^ f[P[j] - 1]; } 361 | /* 362 | * Finally, the new L (the original R) 363 | * is copied back. 364 | */ 365 | for (j = 0; j < 32; j++) { L[j] = tempL[j]; } 366 | } 367 | /* 368 | * The output L and R are reversed. 369 | */ 370 | for (j = 0; j < 32; j++) { 371 | t = L[j]; 372 | L[j] = R[j]; 373 | R[j] = t; 374 | } 375 | /* 376 | * The final output 377 | * gets the inverse permutation of the very original. 378 | */ 379 | for (j = 0; j < 32; ++j) { 380 | perm[j] = L[j]; 381 | perm[j + 32] = R[j]; 382 | } 383 | for (j = 0; j < 64; j++) { 384 | block[j] = perm[FP[j] - 1]; 385 | } 386 | } 387 | 388 | /** 389 | * Transform a string to an array of bytes 390 | */ 391 | var strToBytes = function(str) { 392 | var i; 393 | var x = []; 394 | for (i = 0; i < str.length; ++i) { 395 | x[i] = str.charCodeAt(i); 396 | } 397 | return x; 398 | }; 399 | 400 | var bytesToStr = function(bytes) { 401 | return String.fromCharCode.apply(String, bytes); 402 | }; 403 | 404 | /** 405 | * Implements the Unix crypt(3) DES-based hash. 406 | * 407 | * @param {Array.|string} pw The string to hash 408 | * @param {Array.|string} salt The salt to use (two character string 409 | * from [a-zA-Z0-9./]). 410 | * @param {boolean=} returnBytes (optional) If true, return an array of bytes; 411 | * otherwise, return a string. 412 | * @returns {string} 413 | */ 414 | function crypt(pw, salt, returnBytes) { 415 | if (typeof (pw) === 'string') pw = strToBytes(pw); 416 | if (typeof (salt) === 'string') salt = strToBytes(salt); 417 | 418 | var i, j, k, c, temp; 419 | var block = []; 420 | var iobuf = []; 421 | for (i = 0; i < 66; i++) { block[i] = 0; } 422 | for (i = 0, k = 0; 423 | (c = pw[k]) && i < 64; ++k) { 424 | for (j = 0; j < 7; j++, i++) { block[i] = (c >> (6 - j)) & 1; } 425 | i++; 426 | } 427 | 428 | setkey(block); 429 | 430 | for (i = 0; i < 66; i++) { block[i] = 0; } 431 | 432 | for (i = 0; i < 48; i++) { E[i] = e[i]; } 433 | 434 | for (i = 0, k = 0; i < 2; i++, ++k) { 435 | c = salt[k]; 436 | iobuf[i] = c; 437 | if (c > 'Z'.charCodeAt(0)) c -= 6; 438 | if (c > '9'.charCodeAt(0)) c -= 7; 439 | c -= '.'.charCodeAt(0); 440 | for (j = 0; j < 6; j++) { 441 | if ((c >> j) & 1) { 442 | temp = E[6 * i + j]; 443 | E[6 * i + j] = E[6 * i + j + 24]; 444 | E[6 * i + j + 24] = temp; 445 | } 446 | } 447 | } 448 | 449 | for (i = 0; i < 25; i++) { encrypt(block, 0); } 450 | 451 | for (i = 0; i < 11; i++) { 452 | c = 0; 453 | for (j = 0; j < 6; j++) { 454 | c <<= 1; 455 | c |= block[6 * i + j]; 456 | } 457 | c += '.'.charCodeAt(0); 458 | if (c > '9'.charCodeAt(0)) c += 7; 459 | if (c > 'Z'.charCodeAt(0)) c += 6; 460 | iobuf[i + 2] = c; 461 | } 462 | if (iobuf[1] === 0) { iobuf[1] = iobuf[0]; } 463 | 464 | if (returnBytes) return (iobuf); 465 | else return bytesToStr(iobuf); 466 | } 467 | 468 | module.exports = crypt; 469 | -------------------------------------------------------------------------------- /lib/html-escape.js: -------------------------------------------------------------------------------- 1 | // HTML escape utility, but **without** the escaping of the single quote 2 | // character. 3 | // 4 | // Oddly enough, 4chan does not escape the single quote character in tripcodes. 5 | // This means we need to use a custom HTML escape function instead of an 6 | // existing HTML escape module like `he` (since all existing modules escape the 7 | // single quote character). 8 | var escapeMap = { 9 | '&': '&', 10 | '<': '<', 11 | '"': '"', 12 | // '\'': ''', 13 | '>': '>' 14 | }; 15 | 16 | /** 17 | * @param {string} str 18 | * @returns {string} 19 | */ 20 | module.exports = function(str) { 21 | return str.replace(/[&<>"]/g, function(c) { 22 | return escapeMap[c]; 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tripcode", 3 | "version": "4.0.0", 4 | "description": "Calculate tripcodes.", 5 | "keywords": [ 6 | "tripcode" 7 | ], 8 | "homepage": "https://kenany.github.io/tripcode/", 9 | "bugs": { 10 | "url": "https://github.com/kenany/tripcode/issues" 11 | }, 12 | "repository": "github:kenany/tripcode", 13 | "license": "MIT", 14 | "author": "Kenan Yildirim (https://kenany.me/)", 15 | "bin": { 16 | "tripcode": "bin/cmd.js" 17 | }, 18 | "main": "index.js", 19 | "types": "index.d.ts", 20 | "files": [ 21 | "bin", 22 | "lib", 23 | "index.d.ts", 24 | "index.js" 25 | ], 26 | "engines": { 27 | "node": "18 || >=20" 28 | }, 29 | "scripts": { 30 | "lint": "eslint .", 31 | "release": "semantic-release", 32 | "pretest": "npm run -s lint", 33 | "test": "tape test/index.js" 34 | }, 35 | "dependencies": { 36 | "minimist": "^1.2.8", 37 | "split": "^1.0.1" 38 | }, 39 | "devDependencies": { 40 | "@kenan/eslint-config": "^11.1.11", 41 | "@semantic-release/changelog": "^6.0.3", 42 | "@semantic-release/git": "^10.0.1", 43 | "concat-stream": "^2.0.0", 44 | "conventional-changelog-conventionalcommits": "^8.0.0", 45 | "eslint": "^8.57.1", 46 | "graceful-fs": "^4.2.11", 47 | "semantic-release": "^24.2.4", 48 | "tape": "^5.9.0", 49 | "utf8": "^3.0.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/generate-trip-list.js: -------------------------------------------------------------------------------- 1 | const concat = require('concat-stream'); 2 | const fs = require('graceful-fs'); 3 | const path = require('path'); 4 | 5 | module.exports = function(callback) { 6 | const write = concat(function(data) { 7 | const trips = []; 8 | 9 | const lines = data.toString().split('\n'); 10 | lines.forEach((line) => { 11 | const pair = line.split('!'); 12 | if (pair[3]) { 13 | trips.push([pair[0], pair[1]]); 14 | } 15 | }); 16 | 17 | callback(null, trips); 18 | }); 19 | const quest = fs.createReadStream( 20 | path.resolve(__dirname, './fixtures/tripcodes.txt') 21 | ); 22 | quest.pipe(write); 23 | }; 24 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const utf8 = require('utf8'); 2 | const test = require('tape'); 3 | 4 | const tripcode = require('../'); 5 | 6 | const generateTripList = require('./generate-trip-list'); 7 | 8 | test('huge list of tripcodes (19233 assertions)', function(t) { 9 | generateTripList(function(error, tripcodes) { 10 | if (error) throw error; 11 | 12 | t.plan(1); 13 | 14 | let failed = []; 15 | tripcodes.forEach((trip) => { 16 | const actual = tripcode(trip[0]); 17 | const expected = trip[1]; 18 | failed = [actual, expected]; 19 | return actual === expected; 20 | }); 21 | 22 | t.equal(failed[0], failed[1]); 23 | }); 24 | }); 25 | 26 | test('anything not alphanumeric', function(t) { 27 | const SYMBOLS = [ 28 | ['!', 'KNs1o0VDv6'], 29 | ['@', 'z0MWdctOjE'], 30 | 31 | // How? 32 | ['#', 'u2YjtUz8MU'], 33 | 34 | ['$', 'yflOPYrGcY'], 35 | ['%', '1t98deumW.'], 36 | ['^', 'gBeeWo4hQg'], 37 | ['&', 'MhCJJ7GVT.'], 38 | ['*', 'o8gKYE6H8A'], 39 | ['(', 'SGn2Wwr9CY'], 40 | [')', 'E9k1wjKgHI'], 41 | ['-', 'tHbGiobWdM'], 42 | ['_', 'm3eoQIlU/U'], 43 | ['=', 'wmxP/NHJxA'], 44 | ['+', 'IHLbs/YhoA'], 45 | ['[', '7h2f0/nQ3w'], 46 | [']', 'rjM99frkZs'], 47 | ['{', 'odBt7a7lv6'], 48 | ['}', 'ATNP9hXHcg'], 49 | [';', 'zglc7ct1Ls'], 50 | [':', '.BmRMKOub2'], 51 | ['\'', '8/08awL.AE'], 52 | ['"', 'gt1azVccY2'], 53 | ['<', 'D1YGKrvmeg'], 54 | ['>', 'afqVxck0Ts'], 55 | [',', 'YeQQgdCJE6'], 56 | ['.', 'XONm83jaIU'], 57 | ['\\', '9xUxYS2dlM'], 58 | ['?', 'cPUZU5OGFs'], 59 | [' ', 'wqLZLRuzPQ'] 60 | ]; 61 | 62 | t.plan(SYMBOLS.length); 63 | 64 | SYMBOLS.forEach((trip) => { 65 | t.equal(tripcode(trip[0]), trip[1]); 66 | }); 67 | }); 68 | 69 | test('symbols that are ignored', function(t) { 70 | const SYMBOLS = [ 71 | '©' 72 | ]; 73 | 74 | t.plan(SYMBOLS.length); 75 | 76 | SYMBOLS.forEach((trip) => { 77 | t.equal(tripcode(trip), ''); 78 | }); 79 | }); 80 | 81 | test('collisions', function(t) { 82 | t.plan(1); 83 | 84 | // U+8A1B CJK UNIFIED IDEOGRAPH-8A1B 85 | // http://codepoints.net/U+8A1B 86 | // 87 | // !c8eDXvwFLQ 88 | t.equal(tripcode('fa'), tripcode(utf8.decode('\xE8\xA8\x9B'))); 89 | }); 90 | 91 | test('half width katakana', function(t) { 92 | t.plan(1); 93 | 94 | t.equal(tripcode('ミミ'), '8wihCLEUuc'); 95 | }); 96 | --------------------------------------------------------------------------------