Skip to content

Commit 14281ef

Browse files
feat: Add AER-256
1 parent 9ab0487 commit 14281ef

File tree

7 files changed

+186
-1
lines changed

7 files changed

+186
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ console.log(rot('Hello world!'))
5656
- Wolfenbuetteler code
5757
- Multiplicative cipher
5858
- Affine
59+
- AER-256
5960

6061
## Contributing
6162

docs/ciphers/aer256.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# AER-256
2+
3+
> A widely unknown cipher, mostly seen on "underground" sites and forums.
4+
Neither the origin nor the creator are known. It has large similarities
5+
to [ARMON-64](./armon64.md).
6+
7+
## Cipher behavior information
8+
9+
* Case sensitive? ✓
10+
* Deterministic? ✓
11+
* Alphabet: ```!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ``` (All Unicode Characters using only 2 bytes (U+0000 - U+00FF)
12+
* Characters not in alphabet will: **throw an error**
13+
14+
## Default options object
15+
16+
```
17+
const options = {
18+
key: '' // Must be at least 3 characters long!
19+
}
20+
```
21+
22+
## Usage
23+
24+
### Encoding
25+
26+
#### Default
27+
28+
```
29+
import aer256 from 'cipher-collection'
30+
31+
32+
console.log(aer256.encode('hey', { key: 'ABCDEF' })) // 1432.382960035134
33+
```
34+
35+
### Decoding
36+
37+
#### Default
38+
39+
```
40+
import aer256 from 'cipher-collection'
41+
42+
43+
console.log(aer256.decode('1432.382960035134', { key: 'ABCDEF' })) // hey
44+
```

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@
1818
* [Wolfenbuetteler code](./ciphers/wolfenbuetteler.md)
1919
* [Multiplicative cipher](./ciphers/multiplicative.md)
2020
* [Affine](./ciphers/affine.md)
21+
* [AER-256](./ciphers/aer256.md)
2122

src/aer256.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import aer256ArmonBase from './helpers/aerArmonBase'
2+
3+
const encode = (input, options = {}) => {
4+
options = { ...DEFAULT_OPTIONS, ...options }
5+
options.isAer256 = true
6+
return aer256ArmonBase.encode(input, options)
7+
}
8+
9+
const decode = (input, options = {}) => {
10+
options = { ...DEFAULT_OPTIONS, ...options }
11+
options.isAer256 = true
12+
return aer256ArmonBase.decode(input, options)
13+
}
14+
15+
const DEFAULT_OPTIONS = {
16+
key: '',
17+
failOnUnknownCharacter: true
18+
}
19+
20+
export default {
21+
encode,
22+
decode
23+
}

src/helpers/aerArmonBase.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
const encode = (input, options) => {
2+
if (options.key.length < 3) {
3+
throw Error('Key is too short! It must be at least 3 characters')
4+
}
5+
6+
const heyKey = [...options.key].map(reduceToHexCharCodeConcatenationNumber)
7+
8+
const chunkedString = input.match(new RegExp(`.{1,${Math.round(options.key.length / 2)}}`, 'g'))
9+
10+
const keyLengthRange = Array.from(new Array(options.key.length), (v, i) => i)
11+
12+
return chunkedString.map(s =>
13+
keyLengthRange.reduce((acc, i) => {
14+
switch (i % (options.isAer256 ? 3 : 4)) {
15+
case 0:
16+
acc += heyKey[i]
17+
break
18+
case 1:
19+
acc /= heyKey[i]
20+
break
21+
case 2:
22+
acc -= heyKey[i]
23+
break
24+
case 3:
25+
acc *= 0.01 * heyKey[i]
26+
}
27+
return acc
28+
}, reduceToHexCharCodeConcatenationNumber(s))
29+
).join(options.isAer256 ? ', ' : '+')
30+
}
31+
32+
const decode = (input, options) => {
33+
if (options.key.length < 3) {
34+
throw Error('Key is too short! It must be at least 3 characters')
35+
}
36+
37+
const hexKey = [...options.key].map(reduceToHexCharCodeConcatenationNumber)
38+
39+
const keyLengthRangeReversed = Array.from(new Array(options.key.length), (v, i) => i).reverse()
40+
41+
return input.split(options.isAer256 ? ', ' : '+').map(s => {
42+
// Reverse arithmetic from encoding based on parsed float from string
43+
const float = keyLengthRangeReversed.reduce((acc, i) => {
44+
switch (i % (options.isAer256 ? 3 : 4)) {
45+
case 0:
46+
acc -= hexKey[i]
47+
break
48+
case 1:
49+
acc *= hexKey[i]
50+
break
51+
case 2:
52+
acc += hexKey[i]
53+
break
54+
case 3:
55+
acc /= 0.01 * hexKey[i]
56+
}
57+
return acc
58+
}, parseFloat(s))
59+
60+
// Round output and convert it to hex
61+
return Math.round(float).toString(16)
62+
})
63+
.join('')
64+
// Split up in hex groups of two again and unescape
65+
.match(/.{1,2}/g).map(s => String.fromCharCode(parseInt(s, 16))).join('')
66+
}
67+
68+
/**
69+
* Map each character to hex representation of it's char code, join them together and then retrieve a decimal number
70+
* out of the concatenated hex string.
71+
*/
72+
const reduceToHexCharCodeConcatenationNumber = c => parseInt([...c].map(x => {
73+
const hexCharCode = x.charCodeAt(0).toString(16)
74+
if (hexCharCode.length > 2) {
75+
throw Error('Invalid character')
76+
}
77+
return hexCharCode
78+
}).join(''), 16)
79+
80+
export default {
81+
encode,
82+
decode
83+
}

src/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import base64 from './base64'
88
import wolfenbuetteler from './wolfenbuetteler'
99
import multiplicative from './multiplicative'
1010
import affine from './affine'
11+
import aer256 from './aer256'
1112

1213
export default {
1314
rot,
@@ -19,5 +20,6 @@ export default {
1920
base64,
2021
wolfenbuetteler,
2122
multiplicative,
22-
affine
23+
affine,
24+
aer256
2325
}

test/aer256.test.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import aer256 from 'aer256'
2+
3+
describe('encoding', () => {
4+
test('default', () => {
5+
expect(() => { aer256.encode('1432.382960035134') }).toThrowError('Key is too short! It must be at least 3 characters')
6+
})
7+
test('with key', () => {
8+
expect(aer256.encode('hey', { key: 'ABCDEF' })).toBe('1432.382960035134')
9+
expect(aer256.encode('hey!*A', { key: 'ABCDEF*' })).toBe('384574.7097057532, -25.5959595959596')
10+
expect(aer256.encode('hey!*A!Ü!Ü!Ü!', { key: 'ABCDEF*' })).toBe('384574.7097057532, 155639.92072902943,' +
11+
' 124713.95147123409, -27.963987703118136')
12+
expect(aer256.encode('hey!*Aäää', { key: 'ABCDEF*' })).toBe('384574.7097057532, 155650.8842775582, -27.9211682037769')
13+
})
14+
test('invalid characters', () => {
15+
expect(() => { aer256.encode('💯文', {key: 'ABCDEF*'}) }).toThrowError('Invalid character')
16+
})
17+
})
18+
19+
describe('decoding', () => {
20+
test('default', () => {
21+
expect(() => { aer256.decode('1432.382960035134') }).toThrowError('Key is too short! It must be at least 3 characters')
22+
})
23+
test('with key', () => {
24+
expect(aer256.decode('1432.382960035134', { key: 'ABCDEF' })).toBe('hey')
25+
expect(aer256.decode('384574.7097057532, -25.5959595959596', { key: 'ABCDEF*' })).toBe('hey!*A')
26+
expect(aer256.decode('384574.7097057532, 155639.92072902943, 124713.95147123409, -27.963987703118136', { key: 'ABCDEF*' })).toBe('hey!*A!Ü!Ü!Ü!')
27+
expect(aer256.decode('384574.7097057532, 155650.8842775582, -27.9211682037769', { key: 'ABCDEF*' })).toBe('hey!*Aäää')
28+
// Fails because those characters have larger hex representations than "usual" characters
29+
// expect(aer256.decode('796612.8594642073', { key: 'ABCDEF*' })).toBe('💯文')
30+
})
31+
})

0 commit comments

Comments
 (0)