Skip to content

Instantly share code, notes, and snippets.

@stypr
Last active December 30, 2020 17:29
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save stypr/6aad4123fdea28195a0bf220e2fb8179 to your computer and use it in GitHub Desktop.
Deobfuscated Papago API (Python/Javascript/PHP) : Blocked as of December 2020
<?php
// Ported from papago.py
// v1: b64_enc(rot13([:16]) + [16:])
/* Derived from stackoverflow */
function uuidgen() {
return sprintf('%08x-%04x-%04x-%04x-%04x%08x',
mt_rand(0, 0xffffffff),
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff),
mt_rand(0, 0xffff), mt_rand(0, 0xffffffff)
);
}
/* Derived from stackoverflow */
function contains($str, array $arr){
foreach($arr as $a) {
if (stripos($str,$a) !== false) return true;
}
return false;
}
class PapagoCrypt {
private static function generate_random_string($length=8){
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$charactersLength = strlen($characters);
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $characters[rand(0, $charactersLength - 1)];
}
return $randomString;
}
private static function rotate_string_left($text, $length){
return substr($text, $length) . substr($text, 0, $length);
}
private static function rotate_string_right($text, $length){
return substr($text, -$length) . substr($text, 0, -$length);
}
private static function change_alphabet_order($text){
$table_before = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
$table_after = "NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm";
return strtr($text, $table_before, $table_after);
}
private static function strlen_multibyte($text){
return strlen($text);
}
public function encrypt($plaintext){
$padding_length = self::strlen_multibyte($plaintext) % 6;
$padding_byte = substr(self::generate_random_string(6), 0, 6 - $padding_length);
$random_byte = self::generate_random_string(1);
$encode_text = "" . $plaintext . $padding_byte;
$b64_text = base64_encode($encode_text);
$b64_size = ord($random_byte) % (strlen($b64_text) - 2) + 1;
$rotated_b64_text = $random_byte . self::rotate_string_left($b64_text, $b64_size);
return self::change_alphabet_order($rotated_b64_text);
}
public function decrypt($crypttext){
$rotated_b64_text = self::change_alphabet_order($crypttext);
$random_byte = $rotated_b64_text[0];
$b64_size = ord($random_byte) % (strlen($rotated_b64_text) - 3) + 1;
$b64_text = self::rotate_string_right(substr($rotated_b64_text, 1), $b64_size);
$plaintext = base64_decode($b64_text);
try {
if(strrpos($plaintext, "}") !== false){
return substr($plaintext, 0, strrpos($plaintext, "}") + 1);
}else{
return $plaintext;
}
}catch(Exception $e){
return $plaintext;
}
}
}
class Papago {
private static function parse_large_text($text){
$result = [""];
$data = explode("\n", $text);
$i = 0;
foreach($data as $line){
if(strlen($result[$i] . "\n" . $line) <= 5000){
$result[$i] .= "\n" . $line;
}else{
if(strlen($line) >= 5000){
$_line = $line;
$full_stop = [".", "", "", ":", "", "۔"];
if(!contains($_line, $full_stop)){
$_line = str_split($_line, 5000);
array_push($result, ...$_line);
$i += count($_line);
continue;
}
foreach($full_stop as $_full_stop){
$_line = explode($_full_stop, $_line);
$_line = implode($_full_stop . "\n", $_line);
$_parsed_line = self::parse_large_text($_line);
array_push($result, ...$_parsed_line);
$i += count($_parsed_line);
}
}else{
$result[] = $line;
$i += 1;
}
}
}
return $result;
}
public function translate_full($source, $target, $text){
$result = [];
$url = "https://papago.naver.com/apis/n2mt/translate";
foreach(self::parse_large_text($text) as $_text){
$payload = [
"source" => $source,
"target" => $target,
"text" => $_text,
"deviceId" => uuidgen(),
];
$encrypted_payload = PapagoCrypt::encrypt(json_encode((object)$payload));
$data = ["data" => $encrypted_payload];
$options = array(
"http" => array(
"header" => [
"accept: application/json",
"accept-language: ko",
"content-type: application/x-www-form-urlencoded; charset=UTF-8",
"device-type: pc",
"sec-fetch-dest: empty",
"sec-fetch-mode: cors",
"sec-fetch-site: same-origin",
"x-apigw-partnerid: papago",
"user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) Chrome/82.0.4074.0",
"referer: https://papago.naver.com/",
],
"method" => "POST",
"content" => http_build_query($data)
)
);
$context = stream_context_create($options);
$response = file_get_contents($url, false, $context);
if ($response === FALSE) { continue; }
$result[] = json_decode($response);
}
return $result;
}
public function translate_text($source, $target, $text){
$result = "";
$trans_result = self::translate_full($source, $target, $text);
foreach($trans_result as $data){
$result .= $data->translatedText;
}
return $result;
}
}
var_dump(Papago::translate_text('en', 'ko', 'Hello world!'));
?>
# coding: utf-8
"""
papago.py
Papago Module
"""
import sys
import json
import uuid
import base64
import random
import string
import requests
class PapagoCrypt:
"""
Papago Encryption Module (2020.03)
encrypt(text): Encrypt PapagoCrypt
decrypt(text): Decrypt PapagoCrypt
"""
@staticmethod
def generate_random_string(length):
""" (int) -> str
get random string with a given length
"""
result = ""
i = 0
while i < length:
result += chr(random.randint(43, 122))
i += 1
return result
@staticmethod
def rotate_string_left(text, length):
""" (str, int) -> str
Shift string leftwards, remaining goes to right
"""
return text[length:] + text[0:length]
@staticmethod
def rotate_string_right(text, length):
""" (str, int) -> str
Shift string rightwards, remaining goes to left
"""
return text[-length:] + text[0:-length]
@staticmethod
def change_alphabet_order(text):
""" (str) -> str
Switch alphabet order, replace text with switched order
"""
table_before = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
table_after = "NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm"
if sys.version_info >= (3, 0):
return text.translate(str.maketrans(table_before, table_after))
return text.translate(string.maketrans(table_before, table_after))
@staticmethod
def strlen_multibyte(text):
""" (str) -> int
len(s), but needs to be multibyte. (python2 compatible)
"""
result = 0
for char in text:
_char = ord(char)
result += 1
if 2047 < _char <= 65535:
result += 1
return result
def encrypt(self, plaintext):
""" (str) -> str
Encrypt text for Papago API communication
"""
padding_length = self.strlen_multibyte(plaintext) % 6
padding_byte = (
self.generate_random_string(6)[: 6 - padding_length]
if padding_length > 0
else ""
)
random_byte = self.generate_random_string(1)
# base64 string -> rotate string based on random byte -> change alphabet order
encode_text = "" + plaintext + padding_byte
if sys.version_info >= (3, 0):
b64_text = base64.b64encode(encode_text.encode()).decode()
else:
b64_text = base64.b64encode(encode_text)
rotated_b64_text = random_byte
rotated_b64_text += self.rotate_string_left(
b64_text, ord(random_byte[0]) % (len(b64_text) - 2) + 1
)
return self.change_alphabet_order(rotated_b64_text)
def decrypt(self, crypttext):
""" (PapagoCrypt, str) -> str
Decrypt text for Papago API communication
"""
# change alphabet order -> rotate string based on random byte -> base64 decode
rotated_b64_text = self.change_alphabet_order(crypttext)
random_byte = rotated_b64_text[0]
b64_text = self.rotate_string_right(
rotated_b64_text[1:],
(ord(random_byte[0]) % (len(rotated_b64_text) - 3) + 1),
)
if sys.version_info >= (3, 0):
plaintext = base64.b64decode(b64_text).decode()
else:
plaintext = base64.b64decode(b64_text)
try:
return plaintext[0 : plaintext.rindex("}") + 1]
except IndexError:
return plaintext
class Papago:
"""
Papago API Module
"""
@staticmethod
def parse_large_text(text):
""" (str) -> list
Parse large text (5000 bytes+) based on paragraphs.
"""
max_size = 5000
result = [""]
data = text.split("\n")
i = 0
for line in data:
if len(result[i] + "\n" + line) <= max_size:
result[i] += "\n" + line
else:
# if line is larger than 5000, split by dots
if len(line) >= max_size:
_line = line
_line_len = len(line)
for full_stop in [".", "。", "।", ":", "෴", "۔"]:
_line = "\n".join(
[i + full_stop for i in line.split(full_stop)]
)
# if size is not changed
if _line_len == len(_line):
_line = [_line[i:i + max_size] for i in range(0, len(line), max_size)]
result.extend(_line)
i += len(_line)
continue
_parsed_line = Papago.parse_large_text(_line)
result.extend(_parsed_line)
i += len(_parsed_line)
else:
result.append(line)
i += 1
return result
@classmethod
def translate_full(cls, source, target, text):
""" (Papago, str, str, str) -> str
Translate Text (5000 chars maximum)
"""
result = []
headers = {
"accept": "application/json",
"accept-language": "ko",
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
"device-type": "pc",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"x-apigw-partnerid": "papago",
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) Chrome/82.0.4074.0",
"referer": "https://papago.naver.com/",
}
url = "https://papago.naver.com/apis/n2mt/translate"
for _text in cls.parse_large_text(text):
data = {
"source": source,
"target": target,
"text": _text,
"deviceId": str(uuid.uuid4()),
}
encrypted_data = PapagoCrypt().encrypt(json.dumps(data))
result.append(requests.post(url, "data=%s" % (encrypted_data), headers=headers).json())
return result
@classmethod
def translate_text(cls, source, target, text):
""" (Papago, str) -> str
Translate Text (text data only.)
"""
result = ""
trans_result = cls.translate_full(source, target, text)
for data in trans_result:
result += data['translatedText']
return result
if __name__ == "__main__":
# BIG_FILE = open("long_text.txt", "r").read()
# print(Papago.translate_text('en', 'ko', BIG_FILE))
print(Papago.translate_text('ja', 'ko', 'みくる可愛い'))
/*
Just for decrypting the API..
*/
// derived from https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
var uuidv4 = function() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
// deobfuscated from papago.naver.com
var papago_encrypt = function(plaintext) {
"use strict";
function generate_random_string(len) {
// get random string with size `len`
for (var result = "", i = 0; i < len; i++)
result += String.fromCharCode(Math.floor(80 * Math.random() + 43))
return result;
}
function rotate_string_left(str, len) {
// rotate string `n` times char, removed elements go right
return str.substr(len) + str.substr(0, len);
}
function change_alphabet_order(str) {
// change alphabet order from the string
// > g('abcdefghijklmnopqrstuvwxyz')
// < "nopqrstuvwxyzabcdefghijklm"
return str.replace(/([a-m])|([n-z])/gi, function(e, a, t) {
return String.fromCharCode(a ? a.charCodeAt(0) + 13 : t ? t.charCodeAt(0) - 13 : 0) || e
});
}
function strlen_multibyte(str) {
// multibyte strlen. Korean and Japanese are counted as 3
// > strlen_multibyte('mikuru미쿠루みくる')
// < 24
for (var result = str.length, i = str.length - 1; i >= 0; i--) {
var _char = str.charCodeAt(i);
_char > 127 && _char <= 2047 ? result++ : _char > 2047 && _char <= 65535 && (result += 2)
}
return result;
}
var a = strlen_multibyte(plaintext) % 6
var t = a > 0 ? generate_random_string(6).substr(0, 6 - a) : "";
var encoded_text = btoa("" + plaintext + t);
var random_byte = generate_random_string(1);
var rotated_encoded_text = random_byte + rotate_string_left(encoded_text, random_byte.charCodeAt(0) % (encoded_text.length - 2) + 1);
return change_alphabet_order(rotated_encoded_text);
}
// Rewritten code to decrypt encryption
var papago_decrypt = function(crypttext) {
"use strict";
function generate_random_string(len) {
// get random string with size `len`
for (var result = "", i = 0; i < len; i++)
result += String.fromCharCode(Math.floor(80 * Math.random() + 43))
return result;
}
function rotate_string_right(str, len) {
// rotate string `n` times char, removed elements go left
// return str.substr(len) + str.substr(0, len);
return str.slice(-len) + str.substr(0, str.length - len);
}
function change_alphabet_order(str) {
// change alphabet order from the string
// > g('abcdefghijklmnopqrstuvwxyz')
// < "nopqrstuvwxyzabcdefghijklm"
return str.replace(/([a-m])|([n-z])/gi, function(e, a, t) {
return String.fromCharCode(a ? a.charCodeAt(0) + 13 : t ? t.charCodeAt(0) - 13 : 0) || e
});
}
function strlen_multibyte(str) {
// multibyte strlen. Korean and Japanese are counted as 3
// > strlen_multibyte('mikuru미쿠루みくる')
// < 24
for (var result = str.length, i = str.length - 1; i >= 0; i--) {
var _char = str.charCodeAt(i);
_char > 127 && _char <= 2047 ? result++ : _char > 2047 && _char <= 65535 && (result += 2)
}
return result;
}
var rotated_text = change_alphabet_order(crypttext);
var random_byte = rotated_text[0];
var reordered_text = rotate_string_right(rotated_text.substring(1), random_byte.charCodeAt(0) % (rotated_text.substring(1).length - 2) + 1);
var plaintext = atob(reordered_text);
if(plaintext.startsWith("{")){
try{
return plaintext.substring(0, plaintext.lastIndexOf("}")).concat("}");
}catch(e){ }
}
return plaintext;
}
payload = {
"source": "en",
"target": "ko",
"text": "hi",
"deviceId": uuidv4()
}
fetch("https://papago.naver.com/apis/n2mt/translate",
{
"headers": {
"accept": "application/json",
"accept-language": "ko",
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
"device-type": "pc",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"x-apigw-partnerid": "papago"
},
"referrer": "https://papago.naver.com/",
"referrerPolicy": "origin",
"body": "data=" + papago_encrypt(JSON.stringify(payload)),
"method": "POST",
"mode": "cors",
"credentials": "include"
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment