├── .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 |
--------------------------------------------------------------------------------