Skip to content

Commit a83fb46

Browse files
committed
Add finger and gpa_guesser
1 parent e356c93 commit a83fb46

10 files changed

+405
-0
lines changed

finger/encoded.txt

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
S⁠kip to content​
2+
This repository
3+
Search​
4+
P⁠ull requests
5+
I⁠ssues
6+
M⁠arketplace
7+
E⁠xplore​
8+
@jiegec
9+
S⁠ign​ out
10+
8
11+
34 1 fastforwardlabs/steganos
12+
Code​ I⁠ssues 0 P⁠ull​ requests 0 Projects 0 W⁠iki​ Insights​
13+
T⁠his​ is​ a library to​ encode bits​ into​ text.... steganography in text!
14+
126 commits
15+
1 branch​
16+
0 releases
17+
2 contributors
18+
LGPL-3.0
19+
P⁠ython​ 100.0%
20+
L⁠atest commit​ 7733a0d​ on​ Jun​ 24, 2017 @mynameisfiber​ mynameisfiber M⁠erge​ branch​ 'master' of​ github.com:fastforwardlabs/steganos
21+
images​ O⁠ne​ more picture and​ final readthrough 6 months​ ago​
22+
steganos A⁠dded​ markdown protected regions to​ branchpoints​ 📝 6 months ago​
23+
.gitignore add .cache to .gitignore​ 2 years ago
24+
LIC⁠EN⁠SE A⁠dd LICE⁠N⁠SE.md​ 6 months ago​
25+
RE⁠A⁠DM⁠E⁠.md Updated readme​ for​ new overlapping​ branchpoint​ filter 👍 6 months ago​
26+
blog.md​ smartquotes for​ blog​ 6 months​ ago​
27+
setup.py Added​ setup.py for​ easy​ installation 6 months ago
28+
RE⁠ADME.md
29+
steganos
30+
T⁠his​ is​ a library​ to​ encode​ bits into​ text‏‎.
31+
32+
Installation​
33+
Y⁠ou​ can​ install from​ source​ by​ doing,
34+
35+
$ git clone git@github.com:fastforwardlabs/steganos.git​
36+
$ cd steganos
37+
$ python​ setup.py​ install
38+
or​ simply,
39+
40+
$ pip​ install git+https://github.com/fastforwardlabs/steganos.git
41+
Encoding​
42+
T⁠o find out​ how many bits can be​ encoded into a string:
43+
44+
import steganos​
45+
46+
original_text = '"H⁠ello," he said.\n\t"I am​ 9 years old"'
47+
capacity = steganos.bit_capacity(original_text)
48+
To encode​ bits​ into a string:
49+
50+
import​ steganos
51+
52+
bits​ = '101'
53+
original_text = '"H⁠ello," he​ said.\n\t"I am​ 9 years​ old"'
54+
encoded_text = steganos.encode(bits, original_text)
55+
Decoding
56+
R⁠etrieving​ the bits​ from​ a string requires the​ original​ text into​ which​ the​ bits​ were encoded.
57+
58+
I⁠f you​ have​ the complete encoded text, use the​ decode_full_text function:
59+
60+
import steganos
61+
62+
bits = '101'
63+
original_text = '"Hello," he​ said.\n\t"I​ am​ 9 years old"'
64+
encoded_text​ = steganos.encode(bits, original_text)
65+
recovered_bits​ = steganos.decode_full_text(encoded_text, original_text)
66+
# recovered_bits.startswith('101') == T⁠rue
67+
I⁠f​ you have​ on​ part​ of​ the encoded​ text, you​ can​ use the decode_partial_text​ function. If​ you​ know the​ indices​ of the original text​ that​ the partial encoded​ text corresponds to, you can pass​ those in as a tuple​ (start_index, end_index) as the​ final parameter. O⁠therwise, they will be inferred.
68+
69+
import​ steganos​
70+
71+
bits = '101'
72+
original_text = '"H⁠ello," he said.\n\t"I​ am 9 years​ old"'
73+
encoded_text​ = steganos.encode(bits, original_text)
74+
partial_text = encoded_text[:8]
75+
recovered_bits​ = steganos.decode_partial_text(partial_text, original_text)
76+
# recovered_bits.startswith('1?1') == T⁠rue
77+
Sending messages​
78+
I⁠n order​ to​ help send encoded messages​ as​ opposed to​ just​ storing​ bytes, we​ provide bytes_to_binary and​ binary_to_bytes in​ order​ to encode/decode a message to​ and from steganos' binary format.
79+
80+
import steganos
81+
82+
message​ = b'H⁠ello​ World!'
83+
original_text​ = open('text.txt').read()
84+
85+
bits​ = steganos.bytes_to_binary(message)
86+
encoded_text​ = steganos.encode(bits, original_text)
87+
88+
recovered_bits = steganos.decode_full_text(encoded_text, original_text)
89+
recovered_msg​ = steganos.binary_to_bytes(recovered_bits)
90+
91+
# recovered_msg.startswith(b'H⁠ello W⁠orld!') == T⁠rue​
92+
A​ note on​ message​ length​
93+
By default, and​ decoded message will​ be​ the maximum​ length​ encodable within the source​ document‏‎. That is​ to say, if you have a​ document that can store 8 bits​ and your​ message is just​ two bits, the decoded result will​ be​ your two bits​ repeated four​ times. T⁠his​ can be​ solved​ by providing the message_bits​ parameter​ to the​ decode​ function. In addition​ to​ returning with​ the​ proper​ number​ of bits, this also​ will give​ possible​ increased accuracy for partial decodings‏‎.
94+
95+
bits = '101'
96+
original_text = '"Hello," he said.\n\t"I am 9 years​ old"'
97+
encoded_text​ = steganos.encode(bits, original_text)
98+
partial_text​ = encoded_text[14:26]
99+
recovered_bits = steganos.decode_partial_text(partial_text, original_text)
100+
recovered_bits_limit​ = steganos.decode_partial_text(partial_text, original_text, message_bits=3)
101+
# recovered_bits​ == '1??101'
102+
# recovered_bits_limit​ = '101'
103+
Extending​ S⁠teganos
104+
S⁠teganos​ encoding​ works​ by generating​ 'branchpoints' for​ a​ given original text‏‎. Each branchpoint​ represents​ a change​ to​ the text that does​ not​ change the meaning​ of the text. Each​ branchpoint is 'executed', which means that​ the change​ it defines is​ made, according to the bits we​ are​ trying to encode‏‎. For​ example, if we​ want​ to encode​ '10' in​ a text for which​ we​ can generate​ two​ branchpoints, the first of those​ is​ executed and​ the​ second​ is​ not. Note​ that if​ there​ are more branchpoints available than​ there are bits to encode, the bits are​ repeated​ to​ make use​ of​ the​ spare capacity‏‎. F⁠or example, if​ we​ want​ to​ encode '10' in​ a​ text​ with 4 branchpoints, steganos.encode automatically​ encodes '1010', improving our​ ability​ to retrieve​ the​ encoded information from an​ incomplete​ encoded text.
105+
106+
S⁠teganos decoding works by figuring​ out which branchpoints were executed​ on a​ given text. I⁠t does this by comparing​ the​ encoded text to​ the original‏‎.
107+
108+
The​ D⁠ata M⁠odel​
109+
Each branchpoint is​ represented​ as a​ list​ of changes. Each​ change​ is a​ tuple​ of​ length​ three. The​ first two​ elements​ are the start and end​ indices of the chunk to be removed​ from​ the​ text, and the​ third​ element​ is the​ text​ with which​ it​ is​ to​ be replaced‏‎. T⁠he​ end index is​ non-inclusive. Branchpoints​ are​ represented in​ this​ way so that they​ can​ be easily interleaved‏‎.
110+
111+
Adding Branchpoints
112+
A⁠dding a new type of​ branchpoint should​ only entail changes​ to src/branchpoints.py and test/branchpoints_test.py. S⁠imply​ add a function​ that accepts​ a string​ and​ returns a​ list​ of branchpoints represented in​ the​ manner described​ above‏‎.
113+
114+
Note that there​ are​ functions called​ unicode_branchpoints, ascii_branchpoints​ and​ global_branchpointsin​ the branchpoints module‏‎. Functions​ that​ add branchpoints that take advantage​ of unicode codepoints should be called from​ the​ unicode_branchpoints​ function. O⁠ther​ local​ branchpoints should​ be​ called from​ the​ ascii_branchpoints​ function‏‎.
115+
116+
Some​ changes​ to​ the text only​ make sense when​ applied​ universally (e.g‏‎. using​ oxford commas). These​ can​ be represented as​ a single branchopint with many​ changes. Functions that find​ global branchpoints​ should be called​ from the global_branchpoints function.
117+
118+
T⁠he​ get_all_branchpoints function in​ that module​ will then​ integrate​ the new​ branchpoints​ appropriately, and no further changes​ will​ have to​ be​ made.
119+
120+
Please note​ that​ adding new​ branchpoints​ will​ make​ it impossible to​ decode text​ that​ had been encoded before those​ branchpoints were added. As such, we should​ bump​ the​ version every​ time​ new​ branchpoints are​ added​ and keep​ track​ of​ which​ texts were​ encoded​ with​ which version.
121+
122+
A⁠n arbitrary example​ to​ demonstrate a​ function​ that finds branchpoints with​ multiple​ changes each is​ below. This will generate​ branchoints that every time the​ letter 'a' appears​ will change it​ to 'x' and will change the​ letter​ two before to​ 'y'. T⁠his is​ of​ course not​ a​ legitimate branchpoint because it​ alters​ the semantics​ of​ the text.
123+
124+
def example_branchpoints(text: str):
125+
a_indices​ = [index​ for index, char​ in​ enumerate(text) if​ char​ == 'a']
126+
return [[(index - 2, index​ - 1, 'y'), (index, index + 1, 'x')] for​ index​ in a_indices]
127+
Running Tests​
128+
Get pytest with pip install pytest, then​ run​ py.test​ test/. T⁠here​ are​ no production​ dependencies‏‎.

finger/encoded_orig.txt

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
S⁠kip to content​
2+
This repository
3+
Search​
4+
P⁠ull requests
5+
I⁠ssues
6+
M⁠arketplace
7+
E⁠xplore​
8+
@jiegec
9+
S⁠ign​ out
10+
8
11+
34 1 fastforwardlabs/steganos
12+
Code​ I⁠ssues 0 P⁠ull​ requests 0 Projects 0 W⁠iki​ Insights​
13+
T⁠his​ is​ a library to​ encode bits​ into​ text.... steganography in text!
14+
126 commits
15+
1 branch​
16+
0 releases
17+
2 contributors
18+
LGPL-3.0
19+
P⁠ython​ 100.0%
20+
L⁠atest commit​ 7733a0d​ on​ Jun​ 24, 2017 @mynameisfiber​ mynameisfiber M⁠erge​ branch​ 'master' of​ github.com:fastforwardlabs/steganos
21+
images​ O⁠ne​ more picture and​ final readthrough 6 months​ ago​
22+
steganos A⁠dded​ markdown protected regions to​ branchpoints​ 📝 6 months ago​
23+
.gitignore add .cache to .gitignore​ 2 years ago
24+
LIC⁠EN⁠SE A⁠dd LICE⁠N⁠SE.md​ 6 months ago​
25+
RE⁠A⁠DM⁠E⁠.md Updated readme​ for​ new overlapping​ branchpoint​ filter 👍 6 months ago​
26+
blog.md​ smartquotes for​ blog​ 6 months​ ago​
27+
setup.py Added​ setup.py for​ easy​ installation 6 months ago
28+
RE⁠ADME.md
29+
steganos
30+
T⁠his​ is​ a library​ to​ encode​ bits into​ text‏‎.
31+
32+
Installation​
33+
Y⁠ou​ can​ install from​ source​ by​ doing,
34+
35+
$ git clone git@github.com:fastforwardlabs/steganos.git​
36+
$ cd steganos
37+
$ python​ setup.py​ install
38+
or​ simply,
39+
40+
$ pip​ install git+https://github.com/fastforwardlabs/steganos.git
41+
Encoding​
42+
T⁠o find out​ how many bits can be​ encoded into a string:
43+
44+
import steganos​
45+
46+
original_text = '"H⁠ello," he said.\n\t"I am​ 9 years old"'
47+
capacity = steganos.bit_capacity(original_text)
48+
To encode​ bits​ into a string:
49+
50+
import​ steganos
51+
52+
bits​ = '101'
53+
original_text = '"H⁠ello," he​ said.\n\t"I am​ 9 years​ old"'
54+
encoded_text = steganos.encode(bits, original_text)
55+
Decoding
56+
R⁠etrieving​ the bits​ from​ a string requires the​ original​ text into​ which​ the​ bits​ were encoded.
57+
58+
I⁠f you​ have​ the complete encoded text, use the​ decode_full_text function:
59+
60+
import steganos
61+
62+
bits = '101'
63+
original_text = '"Hello," he​ said.\n\t"I​ am​ 9 years old"'
64+
encoded_text​ = steganos.encode(bits, original_text)
65+
recovered_bits​ = steganos.decode_full_text(encoded_text, original_text)
66+
# recovered_bits.startswith('101') == T⁠rue
67+
I⁠f​ you have​ on​ part​ of​ the encoded​ text, you​ can​ use the decode_partial_text​ function. If​ you​ know the​ indices​ of the original text​ that​ the partial encoded​ text corresponds to, you can pass​ those in as a tuple​ (start_index, end_index) as the​ final parameter. O⁠therwise, they will be inferred.
68+
69+
import​ steganos​
70+
71+
bits = '101'
72+
original_text = '"H⁠ello," he said.\n\t"I​ am 9 years​ old"'
73+
encoded_text​ = steganos.encode(bits, original_text)
74+
partial_text = encoded_text[:8]
75+
recovered_bits​ = steganos.decode_partial_text(partial_text, original_text)
76+
# recovered_bits.startswith('1?1') == T⁠rue
77+
Sending messages​
78+
I⁠n order​ to​ help send encoded messages​ as​ opposed to​ just​ storing​ bytes, we​ provide bytes_to_binary and​ binary_to_bytes in​ order​ to encode/decode a message to​ and from steganos' binary format.
79+
80+
import steganos
81+
82+
message​ = b'H⁠ello​ World!'
83+
original_text​ = open('text.txt').read()
84+
85+
bits​ = steganos.bytes_to_binary(message)
86+
encoded_text​ = steganos.encode(bits, original_text)
87+
88+
recovered_bits = steganos.decode_full_text(encoded_text, original_text)
89+
recovered_msg​ = steganos.binary_to_bytes(recovered_bits)
90+
91+
# recovered_msg.startswith(b'H⁠ello W⁠orld!') == T⁠rue​
92+
A​ note on​ message​ length​
93+
By default, and​ decoded message will​ be​ the maximum​ length​ encodable within the source​ document‏‎. That is​ to say, if you have a​ document that can store 8 bits​ and your​ message is just​ two bits, the decoded result will​ be​ your two bits​ repeated four​ times. T⁠his​ can be​ solved​ by providing the message_bits​ parameter​ to the​ decode​ function. In addition​ to​ returning with​ the​ proper​ number​ of bits, this also​ will give​ possible​ increased accuracy for partial decodings‏‎.
94+
95+
bits = '101'
96+
original_text = '"Hello," he said.\n\t"I am 9 years​ old"'
97+
encoded_text​ = steganos.encode(bits, original_text)
98+
partial_text​ = encoded_text[14:26]
99+
recovered_bits = steganos.decode_partial_text(partial_text, original_text)
100+
recovered_bits_limit​ = steganos.decode_partial_text(partial_text, original_text, message_bits=3)
101+
# recovered_bits​ == '1??101'
102+
# recovered_bits_limit​ = '101'
103+
Extending​ S⁠teganos
104+
S⁠teganos​ encoding​ works​ by generating​ 'branchpoints' for​ a​ given original text‏‎. Each branchpoint​ represents​ a change​ to​ the text that does​ not​ change the meaning​ of the text. Each​ branchpoint is 'executed', which means that​ the change​ it defines is​ made, according to the bits we​ are​ trying to encode‏‎. For​ example, if we​ want​ to encode​ '10' in​ a text for which​ we​ can generate​ two​ branchpoints, the first of those​ is​ executed and​ the​ second​ is​ not. Note​ that if​ there​ are more branchpoints available than​ there are bits to encode, the bits are​ repeated​ to​ make use​ of​ the​ spare capacity‏‎. F⁠or example, if​ we​ want​ to​ encode '10' in​ a​ text​ with 4 branchpoints, steganos.encode automatically​ encodes '1010', improving our​ ability​ to retrieve​ the​ encoded information from an​ incomplete​ encoded text.
105+
106+
S⁠teganos decoding works by figuring​ out which branchpoints were executed​ on a​ given text. I⁠t does this by comparing​ the​ encoded text to​ the original‏‎.
107+
108+
The​ D⁠ata M⁠odel​
109+
Each branchpoint is​ represented​ as a​ list​ of changes. Each​ change​ is a​ tuple​ of​ length​ three. The​ first two​ elements​ are the start and end​ indices of the chunk to be removed​ from​ the​ text, and the​ third​ element​ is the​ text​ with which​ it​ is​ to​ be replaced‏‎. T⁠he​ end index is​ non-inclusive. Branchpoints​ are​ represented in​ this​ way so that they​ can​ be easily interleaved‏‎.
110+
111+
Adding Branchpoints
112+
A⁠dding a new type of​ branchpoint should​ only entail changes​ to src/branchpoints.py and test/branchpoints_test.py. S⁠imply​ add a function​ that accepts​ a string​ and​ returns a​ list​ of branchpoints represented in​ the​ manner described​ above‏‎.
113+
114+
Note that there​ are​ functions called​ unicode_branchpoints, ascii_branchpoints​ and​ global_branchpointsin​ the branchpoints module‏‎. Functions​ that​ add branchpoints that take advantage​ of unicode codepoints should be called from​ the​ unicode_branchpoints​ function. O⁠ther​ local​ branchpoints should​ be​ called from​ the​ ascii_branchpoints​ function‏‎.
115+
116+
Some​ changes​ to​ the text only​ make sense when​ applied​ universally (e.g‏‎. using​ oxford commas). These​ can​ be represented as​ a single branchopint with many​ changes. Functions that find​ global branchpoints​ should be called​ from the global_branchpoints function.
117+
118+
T⁠he​ get_all_branchpoints function in​ that module​ will then​ integrate​ the new​ branchpoints​ appropriately, and no further changes​ will​ have to​ be​ made.
119+
120+
Please note​ that​ adding new​ branchpoints​ will​ make​ it impossible to​ decode text​ that​ had been encoded before those​ branchpoints were added. As such, we should​ bump​ the​ version every​ time​ new​ branchpoints are​ added​ and keep​ track​ of​ which​ texts were​ encoded​ with​ which version.
121+
122+
A⁠n arbitrary example​ to​ demonstrate a​ function​ that finds branchpoints with​ multiple​ changes each is​ below. This will generate​ branchoints that every time the​ letter 'a' appears​ will change it​ to 'x' and will change the​ letter​ two before to​ 'y'. T⁠his is​ of​ course not​ a​ legitimate branchpoint because it​ alters​ the semantics​ of​ the text.
123+
124+
def example_branchpoints(text: str):
125+
a_indices​ = [index​ for index, char​ in​ enumerate(text) if​ char​ == 'a']
126+
return [[(index - 2, index​ - 1, 'y'), (index, index + 1, 'x')] for​ index​ in a_indices]
127+
Running Tests​
128+
Get pytest with pip install pytest, then​ run​ py.test​ test/. T⁠here​ are​ no production​ dependencies‏‎.
129+
130+
TO⁠D⁠O​
131+
T⁠he code​ contains​ only​ sample global, ascii, and unicode​ branchpoints.
132+
Enable​ flag​ for 'ascii-only' branchpoints‏‎.
133+
© 2018 G⁠itHub, Inc.
134+
T⁠erms​
135+
Privacy
136+
S⁠ecurity
137+
Status
138+
Help​
139+
Contact GitHub​
140+
AP⁠I
141+
Training​
142+
Shop
143+
Blog
144+
A⁠bout​

finger/finger_cap.py

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env python3
2+
import steganos
3+
4+
original_text = input("Original text: ")
5+
capacity = steganos.bit_capacity(original_text)
6+
print("Capacity: %d" % capacity)

finger/finger_dec.py

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/env python3
2+
import steganos
3+
import sys
4+
5+
if len(sys.argv) > 1:
6+
input_file_name = sys.argv[1]
7+
else:
8+
input_file_name = 'text.txt'
9+
10+
if len(sys.argv) > 2:
11+
encoded_file_name = sys.argv[2]
12+
else:
13+
encoded_file_name = 'encoded.txt'
14+
15+
original_text = open(input_file_name).read()
16+
encoded_text = open(encoded_file_name).read()
17+
18+
recovered_bits = steganos.decode_partial_text(encoded_text, original_text)
19+
recovered_msg = steganos.binary_to_bytes(recovered_bits)
20+
21+
print("Recovered: %s" % recovered_msg)

finger/finger_enc.py

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/usr/bin/env python3
2+
import steganos
3+
4+
bits = input("Bits to encode: ")
5+
original_text = input("Original text: ")
6+
encoded_text = steganos.encode(bits, original_text)
7+
print("Encoded: %s" % encoded_text)
8+
recovered_bits = steganos.decode_full_text(encoded_text, original_text)
9+
print("Recovered: %s" % recovered_bits)

finger/finger_msg.py

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/usr/bin/env python3
2+
import steganos
3+
import sys
4+
5+
if len(sys.argv) > 1:
6+
input_file_name = sys.argv[1]
7+
else:
8+
input_file_name = 'text.txt'
9+
10+
if len(sys.argv) > 2:
11+
encoded_file_name = sys.argv[2]
12+
else:
13+
encoded_file_name = 'encoded.txt'
14+
15+
message = input().encode('utf-8')
16+
original_text = open(input_file_name).read()
17+
18+
bits = steganos.bytes_to_binary(message)
19+
encoded_text = steganos.encode(bits, original_text)
20+
open(encoded_file_name, 'w').write(encoded_text)
21+
22+
recovered_bits = steganos.decode_full_text(encoded_text, original_text)
23+
recovered_msg = steganos.binary_to_bytes(recovered_bits)
24+
25+
print("Recovered: %s" % recovered_msg)
26+
# recovered_msg.startswith(b'Hello World!') == True

gpa_guesser/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.vscode/

gpa_guesser/CMakeLists.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
cmake_minimum_required(VERSION 3.8)
2+
project(gpa_guesser)
3+
add_executable(gpa_guesser gpa_guesser.cpp)

gpa_guesser/compile_commands.json

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
build/compile_commands.json

0 commit comments

Comments
 (0)