As part of its Google Cloud, Google offers the Google Translation API with a usage-based cost structure. In addition, there is an undocumented API that can be used without a key, but which denies service after only a few requests. When using the website translation function of Google Chrome, it is noticeable that pages can be translated here in very good quality without any noticeable limitation.
apparently, the advanced nmt model is already used here. but what API does Google Chrome use internally to translate the content and can this API be accessed directly – even on the server-side? to analyze network traffic, tools like Wireshark or Telerik Fiddler are recommended, which can also analyze encrypted traffic. but Chrome even delivers the requests it sends during page translation free of charge: they can be easily viewed via the Chrome DevTools:
If you do a translation, then catch the decisive POST request https://translate.googleapis.com via “Copy > Copy as cURL (bash)” and execute it in a tool like Postman, for example, you can resend the request without any problems:
The meaning of the URL parameters are also largely obvious:
Key | Example Value | Meaning |
anno | 3 | Annotation mode (affects the return format) |
client | te_lib | Client information (varies, via the web interface of Google-Translate the value is “webapp”; affects the return format and the rate limiting) |
format | html | String format (important for translating HTML tags) |
v | 1.0 | Google Translate version number |
key | AIzaSyBOti4mM-6x9WDnZIjIeyEU21OpBXqWBgw | API key (see below) |
logld | vTE_20200210_00 | Protocol version |
sl | en | Source language |
tl | en | Target language |
sp | nmt | ML model |
tc | 1 | unknown |
sr | 1 | unknown |
tk | 709408.812158 | Token (see below) |
fashion | 1 | unknown |
Some request headers are also set – but these can mostly be ignored. After manually deselecting all headers, including those from the user agent , an encoding problem is discovered when entering special characters (here when translating ” Hello World “):
If you reactivate the user agent (which generally doesn’t do any harm), the API delivers UTF-8 encoded characters:
Now that we have all the information we need to use this API outside of Google Chrome, if we change the string to be translated (data field q of the POST request) from, for example, “Hello World” to “Hello World“, we get an error message:
We now retranslate this modified one within Google Chrome using the web page translation function and find that the parameter tk has changed as well as the parameter q (all other parameters have remained the same):
Obviously, it is a string dependent token, whose structure is not easy to see, but when you start the web page translation, the following files are loaded:
- 1 CSS file: translateelement.css
- 4 graphics: translate_24dp.png (2x), gen204 (2x)
- 2 JS files: main_de.js , element_main.js
The two JavaScript files are obfuscated and minified. Tools like JS Nice and de4js are now helping us to make these files more readable. In order to debug them live, we recommend the Chrome Extension Request, which tunnels remote files locally on the fly:
Now we can debug the code ( CORS must be enabled on the local server) The relevant code section for generating the token seems to be hidden in the file element_main.js in this section:
function Bp(a, b) { | |
var c = b.split(“.”); | |
b = Number(c[0]) || 0; | |
for (var d = [], e = 0, f = 0; f < a.length; f++) { | |
var h = a.charCodeAt(f); | |
128 > h ? d[e++] = h : (2048 > h ? d[e++] = h >> 6 | 192 : (55296 == (h & 64512) && f + 1 < a.length && 56320 == (a.charCodeAt(f + 1) & 64512) ? (h = 65536 + ((h & 1023) << 10) + (a.charCodeAt(++f) & 1023), d[e++] = h >> 18 | 240, d[e++] = h >> 12 & 63 | 128) : d[e++] = h >> 12 | 224, d[e++] = h >> 6 & 63 | 128), d[e++] = h & 63 | 128) | |
} | |
a = b; | |
for (e = 0; e < d.length; e++) a += d[e], a = Ap(a, “+-a^+6”); | |
a = Ap(a, “+-3^+b+-f”); | |
a ^= Number(c[1]) || 0; | |
0 > a && (a = (a & 2147483647) + 2147483648); | |
c = a % 1E6; | |
return c.toString() + | |
“.” + (c ^ b) | |
} |
Here, among other things, the text is hashed with the help of some bit shifts. But unfortunately, we are still missing a piece of the puzzle: To the function Bp(), besides the argument a (which is the text to be translated), another argument b is passed – a kind of seed, which seems to change over time to time and which is also included in the hashing. But where does it come from? If we jump to the function call of Bp(), we find the following code section:
Tr.prototype.translate = function (a, b, c, d, e, f, h, k) { | |
var l = this, | |
m = this.a.wc(a), | |
n = { | |
q: b, | |
sl: c, | |
tl: d | |
}; | |
this.h.sp && 0 == this.h.sp.indexOf(“nmt”) || (n.sp = “nmt”); | |
n.tc = e; | |
f && (n.ctt = 1); | |
h && (n.dom = 1); | |
k && (n.sr = 1); | |
n[Dp()] = Bp(b.join(“”), Hq); | |
return this.s ? this.s.b().then(function (r) { | |
null != r && (n.jwtt = r, n.rurl = location.hostname); | |
return l.a.na.send(n, C(Wr(m), l)) | |
}, function (r) { | |
r && l.Vb && l.Vb(r) | |
}) : this.a.na.send(n, m) | |
}; |
The function Hq is previously declared as follows:
Hq = function () { | |
function a(d) { | |
return function () { | |
return d | |
} | |
} | |
var b = String.fromCharCode(107), | |
c = a(String.fromCharCode(116)); | |
b = a(b); | |
c = [c(), c()]; | |
c[1] = b(); | |
return yq[“_c” + c.join(b())] || “” | |
}(), |
Here the Deobfuscater left some rubbish; After we have replaced String.fromCharCode (‘…’) with the respective character strings, remove the obsolete a () and piece together the function calls [c (), c ()] , the result is:
Hq = function () { | |
var b = ‘k’, | |
c = ‘t’; | |
c = [c, c]; | |
c[1] = b(); | |
return yq[‘_c’ + c.join(b())] || ” | |
}(), |
Or even easier:
Hq = function () { | |
return yq[‘_ctkk’] || ” | |
}(), |
The function yq is previously defined as:
var yq = window.google && google.translate && google.translate._const; |
So the seed seems to be in the global object google.translate._const._ctkk, which is available at runtime. But where is it set? In the other, previously loaded JS-file main_en.js at least it is also available at the beginning. For this we add the following at the beginning:
console.log(window.google.translate._const._ctkk); |
In the console we now actually get the current seed:
This leaves Google Chrome itself, which apparently provides the seed, as the last option. Fortunately, its source code (Chromium, including the Translate component) is open source and therefore publicly available. We pull the repository locally and find the call to the TranslateScript :: GetTranslateScriptURL function in the translate_script.cc file in the components / translate / core / browser folder:
GURL TranslateScript::GetTranslateScriptURL() { | |
GURL translate_script_url; | |
// Check if command-line contains an alternative URL for translate service. | |
const base::CommandLine& command_line = | |
*base::CommandLine::ForCurrentProcess(); | |
if (command_line.HasSwitch(translate::switches::kTranslateScriptURL)) { | |
translate_script_url = GURL(command_line.GetSwitchValueASCII( | |
translate::switches::kTranslateScriptURL)); | |
if (!translate_script_url.is_valid() || | |
!translate_script_url.query().empty()) { | |
LOG(WARNING) << “The following translate URL specified at the “ | |
<< “command-line is invalid: “ | |
<< translate_script_url.spec(); | |
translate_script_url = GURL(); | |
} | |
} | |
// Use default URL when command-line argument is not specified, or specified | |
// URL is invalid. | |
if (translate_script_url.is_empty()) | |
translate_script_url = GURL(kScriptURL); | |
translate_script_url = net::AppendQueryParameter( | |
translate_script_url, | |
kCallbackQueryName, | |
kCallbackQueryValue); | |
translate_script_url = net::AppendQueryParameter( | |
translate_script_url, | |
kAlwaysUseSslQueryName, | |
kAlwaysUseSslQueryValue); | |
translate_script_url = net::AppendQueryParameter( | |
translate_script_url, | |
kCssLoaderCallbackQueryName, | |
kCssLoaderCallbackQueryValue); | |
translate_script_url = net::AppendQueryParameter( | |
translate_script_url, | |
kJavascriptLoaderCallbackQueryName, | |
kJavascriptLoaderCallbackQueryValue); | |
translate_script_url = AddHostLocaleToUrl(translate_script_url); | |
translate_script_url = AddApiKeyToUrl(translate_script_url); | |
return translate_script_url; | |
} |
The variable with the URL is hard defined in the same file:
const char TranslateScript::kScriptURL[] = | |
“https://translate.googleapis.com/translate_a/element.js”; |
If we now examine the element.js file more closely (after deobfuscating again), we find the hard-set entry c._ctkk – the google.translate object is also set accordingly and the loading of all relevant assets (which we have already discovered earlier) is triggered:
function _setupNS(b) { | |
b = b.split(“.”); | |
for (var a = window, c = 0; c < b.length; ++c) a.hasOwnProperty ? a.hasOwnProperty(b[c]) ? a = a[b[c]] : a = a[b[c]] = {} : a = a[b[c]] || (a[b[c]] = {}); | |
return a | |
} | |
window.addEventListener && “undefined” == typeof document.readyState && window.addEventListener(“DOMContentLoaded”, function () { | |
document.readyState = “complete” | |
}, !1); | |
if (_isNS(‘google.translate.Element’)) { | |
return | |
}(function () { | |
var c = _setupNS(‘google.translate._const’); | |
c._cest = gtConstEvalStartTime; | |
gtConstEvalStartTime = undefined; | |
c._cl = ‘de’; | |
c._cac = ”; | |
c._cam = ”; | |
c._ctkk = ‘440159.776620256’; | |
var h = ‘translate.googleapis.com’; | |
var s = (true ? ‘https’ : window.location.protocol == ‘https:’ ? ‘https’ : ‘http’) + ‘://’; | |
var b = s + h; | |
c._pah = h; | |
c._pas = s; | |
c._pbi = b + ‘/translate_static/img/te_bk.gif’; | |
c._pci = b + ‘/translate_static/img/te_ctrl3.gif’; | |
c._pli = b + ‘/translate_static/img/loading.gif’; | |
c._plla = h + ‘/translate_a/l’; | |
c._pmi = b + ‘/translate_static/img/mini_google.png’; | |
c._ps = b + ‘/translate_static/css/translateelement.css’; | |
c._puh = ‘translate.google.com’; | |
_loadCss(c._ps); | |
_loadJs(b + ‘/translate_static/js/element/main_de.js’); | |
})(); |
Now the parameter key remains for consideration (with the value AIzaSyBOti4mM-6x9WDnZIjIeyEU21OpBXqWBgw). That seems to be a generic browser API key (which can also be found in some Google results ). It is set in Chromium in the file translate_url_util.cc in the folder components / translate / core / browser:
GURL AddApiKeyToUrl(const GURL& url) { | |
return net::AppendQueryParameter(url, kApiKeyName, google_apis::GetAPIKey()); | |
} |
The key is generated in google_apis / google_api_keys.cc from a dummy value:
#if !defined(GOOGLE_API_KEY) | |
#define GOOGLE_API_KEY DUMMY_API_TOKEN | |
#endif |
However, a test shows that the API calls work the same without this key parameter. If you experiment with the API, you will get the status code 200 back if you are successful. If you then run into a limit, you get the status code 411 back with the message ” POST requests require a content-length header “. It is therefore advisable to include this header (which is automatically set as a temporary header in Postman).
The return format of the translated strings is unusual when there are several sentences in one request, and the individual sentences are enclosed by the i-/b-HTML tags:
Also, Google Chrome does not send all the HTML to the API, but saves attribute values such as href in the request (and sets indexes instead, so that the tags can be assigned again later on the client side):
If you change the value of the POST key client from te_lib (Google Chrome) on webapp ( Google Translation website ), you get the final translated string:
The problem is that you are much more likely to run into rate limiting than via te_lib (for comparison: with webapp this is reached after 40,000 chars, with te_lib there is no rate limiting). So we need to take a closer look at how Chrome parses the result. We’ll find it here in element_main.js:
x.Pf = function (a, b) { | |
b.g && this.l.remove(b.f); | |
if (!this.b) return !1; | |
if (this.l.has(b.ea(), !1)) { | |
var c = this.l; | |
if (c.has(b.f, !1)) { | |
var d = b.f, | |
e = c.a[d]; | |
e || (e = c.b[d], c.a[d] = e); | |
b.b = e; | |
b.K = !0 | |
} else c.remove(b.f), b.g = !0; | |
zt(b) | |
} else if (c = this.l, b.g) c.remove(b.f); | |
else if (b.o) { | |
d = b.o.replace(/<a /g, “<span “).replace(/\/a>/g, “/span>”); | |
b.K = !0; | |
delete b.o; | |
b.o = null; | |
b.b = []; | |
e = jg(document, Za); | |
Q(e, !1); | |
e.innerHTML = 0 <= d.indexOf(“<i>”) ? d : “<b>” + d + “</b>”; | |
document.body.appendChild(e); | |
d = []; | |
var f; | |
for (f = e.firstChild; f; f = f.nextSibling) | |
if (“I” == | |
f.tagName) var h = yt(b, Kg(f), f.innerHTML); | |
else if (“B” == f.tagName) { | |
h || (h = yt(b, “”, “”)); | |
if (1 == b.a.length) xt(h.$, d, 0, f); | |
else { | |
var k = d; | |
var l = f; | |
var m = b.a; | |
h = h.$; | |
for (var n = [], r, w = l.firstChild; w; w = r) r = w.nextSibling, Ct(w); | |
for (r = l.firstChild; r; r = r.nextSibling) r.attributes && r.attributes.i ? (l = parseInt(r.attributes.i.nodeValue, 10), !isNaN(l) && 0 <= l && l < m.length && (m[l].ee && n[l] ? n[l].T += r.firstChild && 3 == r.firstChild.nodeType ? r.firstChild.nodeValue : Kg(r) : n[l] = xt(h, k, l, r))) : 3 == r.nodeType && h.push({ | |
R: -1, | |
T: De(r.nodeValue) | |
}); | |
null != h && 0 < h.length && -1 == h[0].R && (1 == h.length ? h[0].R = 0 : (h[1].T = h[0].T + h[1].T, h.shift())) | |
} | |
h = void 0 | |
} | |
f = b.b; | |
for (k = 0; k < f.length – 1; ++k) m = f[k], h = ze(m.$[m.$.length – 1].T), h = h.charCodeAt(h.length – 1), 12288 <= h && 12351 >= h || 65280 <= h && 65519 >= h || (m.$[m.$.length – 1].T += ” “); | |
sg(e); | |
for (e = 0; e < b.a.length; ++e) e < d.length && e < b.l.length && null != d[e] && (f = b.l[e], k = d[e].start, null != k && (m = f.substring(0, f.length – ye(f).length), ” ” == m && (m = “”), m && (k.T = m + ye(k.T))), k = d[e].end, null != k && (f = f.substring(ze(f).length), ” ” == f && (f = “”), f && (k.T = | |
ze(k.T) + f))); | |
1 != b.b.length || b.b[0].lf || (b.b[0].lf = b.f); | |
c.write(b.f, b.b); | |
zt(b) | |
} | |
b.H || (this.W = !1); | |
c = b.g ? !0 : void 0; | |
a.K += b.G; | |
null != c && (a.qa = !0); | |
b = Math.min(Math.floor(100 * a.K / a.f), 100); | |
if (a.o != b || c) a.o = b, a.L ? (a.l(a.o, !0, c), a.W(a.K)) : a.l(a.o, !1, c); | |
return !1 | |
}; |
If you send the entire HTML code to the API, it leaves the attributes in the translated response. We therefore do not have to imitate the entire parse behavior, but only extract the final, translated string from the response. To do this, we build a small HTML tag parser that discards the outermost <i> tags including their content and removes the outermost <b> tags. With this in mind, we can now build a server-side version of the translation API:
<?php | |
require_once __DIR__ . ‘/vendor/autoload.php’; | |
use vielhuber\stringhelper\__; | |
use Faker\Factory; | |
class GoogleTranslate | |
{ | |
function translate($string) | |
{ | |
$string = $this->parseResultPre($string); | |
$args = [ | |
‘anno’ => 3, | |
‘client’ => ‘te_lib’, | |
‘format’ => ‘html’, | |
‘v’ => ‘1.0’, | |
‘key’ => ‘AIzaSyBOti4mM-6x9WDnZIjIeyEU21OpBXqWBgw’, | |
‘logld’ => ‘vTE_20200210_00’, | |
‘sl’ => ‘de’, | |
‘tl’ => ‘en’, | |
‘sp’ => ‘nmt’, | |
‘tc’ => 1, | |
‘sr’ => 1, | |
‘tk’ => $this->generateTk($string, $this->generateTkk()), | |
‘mode’ => 1 | |
]; | |
$response = __::curl( | |
‘https://translate.googleapis.com/translate_a/t?’ . http_build_query($args), | |
[‘q’ => $string], | |
‘POST’, | |
[ | |
‘User-Agent’ => | |
‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36’, | |
‘Content-Length’ => strlen(‘q=’ . urlencode($string)) | |
], | |
false, | |
false, | |
3 | |
); | |
return [‘result’ => $this->parseResultPost($response->result), ‘status’ => $response->status]; | |
} | |
private function parseResultPre($input) | |
{ | |
// google sometimes surrounds the translation with <i> and <b> tags | |
// do distinguish real i-/b-tags, replace them (we undo that later on) | |
$dom = self::str_to_dom($input); | |
$xpath = new \DOMXPath($dom); | |
foreach ([‘i’, ‘b’] as $tags__value) { | |
foreach ($dom->getElementsByTagName($tags__value) as $divs__value) { | |
$divs__value->setAttribute(‘data-native’, ‘true’); | |
} | |
} | |
$nodes = $xpath->query(‘/html/body//*’); | |
if (count($nodes) > 0) { | |
$id = 1; | |
foreach ($nodes as $nodes__value) { | |
$nodes__value->setAttribute(‘gtid’, $id); | |
$id++; | |
} | |
} | |
$output = self::dom_to_str($dom); | |
return $output; | |
} | |
private function parseResultPost($input) | |
{ | |
// discard the (outer) <i>-tags and take the content of the <b>-tags | |
$output = ”; | |
$pointer = 0; | |
$lvl_i = 0; | |
$lvl_i_inner = 0; | |
$lvl_b = 0; | |
$lvl_b_inner = 0; | |
// multibyte split to array of chars | |
foreach (preg_split(‘//u’, $input, -1, PREG_SPLIT_NO_EMPTY) as $chars__value) { | |
if ($pointer >= 3 && mb_substr($input, $pointer – 3, 3) === ‘<i>’) { | |
$lvl_i_inner++; | |
} | |
if ($pointer >= 3 && mb_substr($input, $pointer – 3, 3) === ‘<b>’) { | |
$lvl_b_inner++; | |
} | |
if (mb_substr($input, $pointer, 4) === ‘</i>’ && $lvl_i_inner > 0) { | |
$lvl_i_inner–; | |
} | |
if (mb_substr($input, $pointer, 4) === ‘</b>’ && $lvl_b_inner > 0) { | |
$lvl_b_inner–; | |
} | |
if (mb_substr($input, $pointer, 3) === ‘<i>’) { | |
$lvl_i++; | |
} | |
if (mb_substr($input, $pointer, 3) === ‘<b>’) { | |
$lvl_b++; | |
} | |
if ($pointer >= 4 && mb_substr($input, $pointer – 4, 4) === ‘</i>’ && $lvl_i > 0) { | |
$lvl_i–; | |
} | |
if ($pointer >= 4 && mb_substr($input, $pointer – 4, 4) === ‘</b>’ && $lvl_b > 0) { | |
$lvl_b–; | |
} | |
$pointer++; | |
// discard multiple spaces | |
if ($chars__value === ‘ ‘ && mb_strlen($output) > 0 && mb_substr($output, -1) === ‘ ‘) { | |
continue; | |
} | |
// save | |
if (($lvl_b_inner >= 1 && $lvl_i_inner === 0) || ($lvl_b === 0 && $lvl_i === 0)) { | |
$output .= $chars__value; | |
} | |
} | |
$output = trim($output); | |
$dom = self::str_to_dom($output); | |
$xpath = new \DOMXPath($dom); | |
foreach ([‘i’, ‘b’] as $tags__value) { | |
foreach ($dom->getElementsByTagName($tags__value) as $divs__value) { | |
$divs__value->removeAttribute(‘data-native’); | |
} | |
} | |
// merge neighbour elements with the same id together | |
$nodes = $xpath->query(‘/html/body//*[@gtid]’); | |
if (count($nodes) > 0) { | |
foreach ($nodes as $nodes__value) { | |
if ($nodes__value->hasAttribute(‘please-remove’)) { | |
continue; | |
} | |
$id = $nodes__value->getAttribute(‘gtid’); | |
$html = $nodes__value->nodeValue; | |
$nextSibling = $nodes__value->nextSibling; | |
if ($nextSibling === null) { | |
continue; | |
} | |
if ($nextSibling->nodeName === ‘#text’ && trim($nextSibling->textContent) == ”) { | |
$nextSibling = $nextSibling->nextSibling; | |
} | |
if ($nextSibling === null || $nextSibling->nodeName === ‘#text’) { | |
continue; | |
} | |
$id2 = $nextSibling->getAttribute(‘gtid’); | |
if ($id !== $id2) { | |
continue; | |
} | |
$nextSibling->setAttribute(‘please-remove’, ‘1’); | |
$html .= ‘ ‘ . $nextSibling->nodeValue; | |
$nodes__value->nodeValue = $html; | |
} | |
foreach ($nodes as $nodes__value) { | |
$nodes__value->removeAttribute(‘gtid’); | |
if ($nodes__value->hasAttribute(‘please-remove’)) { | |
$nodes__value->parentNode->removeChild($nodes__value); | |
} | |
} | |
} | |
$output = self::dom_to_str($dom); | |
return $output; | |
} | |
private function generateTkk() | |
{ | |
$cache = sys_get_temp_dir() . ‘/tkk.cache’; | |
if (file_exists($cache) && filemtime($cache) > strtotime(‘now – 1 hour’)) { | |
return file_get_contents($cache); | |
} | |
$data = __::curl(‘https://translate.googleapis.com/translate_a/element.js’, null, ‘GET’); | |
$response = $data->result; | |
$pos1 = mb_strpos($response, ‘c._ctkk=\”) + mb_strlen(‘c._ctkk=\”); | |
$pos2 = mb_strpos($response, ‘\”, $pos1); | |
$tkk = mb_substr($response, $pos1, $pos2 – $pos1); | |
file_put_contents($cache, $tkk); | |
return $tkk; | |
} | |
private function generateTk($f0, $w1) | |
{ | |
// ported from js to php from https://translate.googleapis.com/element/TE_20200210_00/e/js/element/element_main.js | |
$w1 = explode(‘.’, $w1); | |
$n2 = $w1[0]; | |
for ($j3 = [], $t4 = 0, $h5 = 0; $h5 < strlen(mb_convert_encoding($f0, ‘UTF-16LE’, ‘UTF-8’)) / 2; $h5++) { | |
$z6 = | |
ord(mb_convert_encoding($f0, ‘UTF-16LE’, ‘UTF-8’)[$h5 * 2]) + | |
(ord(mb_convert_encoding($f0, ‘UTF-16LE’, ‘UTF-8’)[$h5 * 2 + 1]) << 8); | |
if (128 > $z6) { | |
$j3[$t4++] = $z6; | |
} else { | |
if (2048 > $z6) { | |
$j3[$t4++] = ($z6 >> 6) | 192; | |
} else { | |
if ( | |
55296 == ($z6 & 64512) && | |
$h5 + 1 < strlen(mb_convert_encoding($f0, ‘UTF-16LE’, ‘UTF-8’)) / 2 && | |
56320 == | |
((ord(mb_convert_encoding($f0, ‘UTF-16LE’, ‘UTF-8’)[($h5 + 1) * 2]) + | |
(ord(mb_convert_encoding($f0, ‘UTF-16LE’, ‘UTF-8’)[($h5 + 1) * 2 + 1]) << 8)) & | |
64512) | |
) { | |
$h5++; | |
$z6 = | |
65536 + | |
(($z6 & 1023) << 10) + | |
((ord(mb_convert_encoding($f0, ‘UTF-16LE’, ‘UTF-8’)[$h5 * 2]) + | |
(ord(mb_convert_encoding($f0, ‘UTF-16LE’, ‘UTF-8’)[$h5 * 2 + 1]) << 8)) & | |
1023); | |
$j3[$t4++] = ($z6 >> 18) | 240; | |
$j3[$t4++] = (($z6 >> 12) & 63) | 128; | |
} else { | |
$j3[$t4++] = ($z6 >> 12) | 224; | |
} | |
$j3[$t4++] = (($z6 >> 6) & 63) | 128; | |
} | |
$j3[$t4++] = ($z6 & 63) | 128; | |
} | |
} | |
$f0 = $n2; | |
for ($t4 = 0; $t4 < count($j3); $t4++) { | |
$f0 += $j3[$t4]; | |
$c7 = $f0; | |
$x8 = ‘+-a^+6’; | |
for ($r9 = 0; $r9 < strlen($x8) – 2; $r9 += 3) { | |
$u10 = $x8[$r9 + 2]; | |
$u10 = ‘a’ <= $u10 ? ord($u10[0]) – 87 : intval($u10); | |
$a11 = $c7; | |
$c12 = $u10; | |
if ($c12 >= 32 || $c12 < -32) { | |
$c13 = (int) ($c12 / 32); | |
$c12 = $c12 – $c13 * 32; | |
} | |
if ($c12 < 0) { | |
$c12 = 32 + $c12; | |
} | |
if ($c12 == 0) { | |
return (($a11 >> 1) & 0x7fffffff) * 2 + (($a11 >> $c12) & 1); | |
} | |
if ($a11 < 0) { | |
$a11 = $a11 >> 1; | |
$a11 &= 2147483647; | |
$a11 |= 0x40000000; | |
$a11 = $a11 >> $c12 – 1; | |
} else { | |
$a11 = $a11 >> $c12; | |
} | |
$b14 = $a11; | |
$u10 = ‘+’ == $x8[$r9 + 1] ? $b14 : $c7 << $u10; | |
$c7 = ‘+’ == $x8[$r9] ? ($c7 + $u10) & 4294967295 : $c7 ^ $u10; | |
} | |
$f0 = $c7; | |
} | |
$c7 = $f0; | |
$x8 = ‘+-3^+b+-f’; | |
for ($r9 = 0; $r9 < strlen($x8) – 2; $r9 += 3) { | |
$u10 = $x8[$r9 + 2]; | |
$u10 = ‘a’ <= $u10 ? ord($u10[0]) – 87 : intval($u10); | |
$a11 = $c7; | |
$c12 = $u10; | |
if ($c12 >= 32 || $c12 < -32) { | |
$c13 = (int) ($c12 / 32); | |
$c12 = $c12 – $c13 * 32; | |
} | |
if ($c12 < 0) { | |
$c12 = 32 + $c12; | |
} | |
if ($c12 == 0) { | |
return (($a11 >> 1) & 0x7fffffff) * 2 + (($a11 >> $c12) & 1); | |
} | |
if ($a11 < 0) { | |
$a11 = $a11 >> 1; | |
$a11 &= 2147483647; | |
$a11 |= 0x40000000; | |
$a11 = $a11 >> $c12 – 1; | |
} else { | |
$a11 = $a11 >> $c12; | |
} | |
$b14 = $a11; | |
$u10 = ‘+’ == $x8[$r9 + 1] ? $b14 : $c7 << $u10; | |
$c7 = ‘+’ == $x8[$r9] ? ($c7 + $u10) & 4294967295 : $c7 ^ $u10; | |
} | |
$f0 = $c7; | |
$f0 ^= $w1[1] ? $w1[1] + 0 : 0; | |
if (0 > $f0) { | |
$f0 = ($f0 & 2147483647) + 2147483648; | |
} | |
$f0 = fmod($f0, pow(10, 6)); | |
return $f0 . ‘.’ . ($f0 ^ $n2); | |
} | |
} | |
$gt = new GoogleTranslate(); | |
$faker = Factory::create(‘de_DE’); | |
$chars = 0; | |
for ($i = 0; $i < 1000; $i++) { | |
$orig = $faker->realText(250); | |
$chars += mb_strlen($orig); | |
$response = $gt->translate($orig); | |
logStatus([$response[‘status’], $chars, $orig, $response[‘result’]]); | |
echo $response[‘status’] . ‘: ‘ . $chars . PHP_EOL; | |
} | |
function logStatus($msg) | |
{ | |
file_put_contents(‘log.txt’, date(‘Y-m-d H:i:s’) . “\t” . implode(“\t”, $msg) . PHP_EOL, FILE_APPEND); | |
} |
view raw14.php hosted with ❤ by GitHub
The following are the results of an initial test that was carried out on five different systems with different bandwidths and IP addresses:
Character | Characters per request | Duration | Error rate | Cost via official API |
13.064.662 | ~250 | 03: 36: 17h | 0% | 237,78€ |
24.530.510 | ~250 | 11: 09: 13h | 0% | 446,46€ |
49.060.211 | ~250 | 20:39:10h | 0% | 892,90€ |
99.074.487 | ~1000 | 61: 24: 37h | 0% | 1803,16€ |
99.072.896 | ~1000 | 62:22:20h | 0% | 1803,13€ |
Σ284.802.766 | ~ Ø550 | Σ159:11:37h | 0% | Σ € 5183.41 |
Note: This blog post, including all scripts, was written for testing purposes only; do not use the scripts for productive use, but work with the official Google Translation API instead.
Enjoyed this post about google api hacking?
Why not subscribe to our weekly cybersecurity newsletter?