破解google语音合成接口

破解google语音合成接口

因为个人的一些需求,需要每天把造出来的句子(通过认识新单词,写一些句子),转变成语音,方便熟悉前段时间的单词。 其中不仅是句子,也有作为,所以我一直在寻找较好的,符合英语发音标准的语音合成接口。

很久之前,我做了一个 word_pronunciation 的项目,旨在拉取有道的单词本中的内容,以一个单词一个例句的方式, 合成一个语音片段朗读出来,例句也会有兼顾中英文的选项。

那时单独用了百度的免费体验版的语音合成接口,但效果并不理想,一些英文朗诵出来还是硬邦邦的。怀着遗憾,我发现Google Translation 中的发音非常的不错。而且还可选择语言的口音(英式|美式)进行合成,实在是贴心。

然而,Google并没有那么轻松让第三方破解他的接口并使用,尽管GoogleTranslation也是免费的, 但它的目的是训练NLP模型,通过机器学习加强翻译语料库,完善基础设施。如果直接用语音合成的接口,其实 对它没有什么价值,自然要通过加密防止第三方滥用。

加密的方法,也不算是特别严格,其实是客户端和服务端互相约定一段字符串,通过相同的一种算法加密这段字符串和我们要合成语音的文字,可以得出 一段十进制序列,然后就可以依据这个参数来判断是否是Google Translation的客户端发送出来的。

为什么说不是特别严格的加密方式?因为任何保存在客户端的代码都是不安全的,客户端是一个GUI,不能作为逻辑和安全的主导层, 当然呢,连我这种小渣渣都知道的事情,Google不可能不知道。它也是为了方便,简单所以也没有做那么复杂, 本身这个接口就是对世界开放的,这点钱它也是花得起。

所以我找到了隐藏在它客户端中js代码,是用来做加密处理的,如下:

  function shiftLeftOrRightThenSumOrXor(num, opArray) {
    return opArray.reduce((acc, opString) => {
      let op1 = opString[1]; //    '+' | '-' ~ SUM | XOR
      let op2 = opString[0]; //    '+' | '^' ~ SLL | SRL
      let xd = opString[2]; //    [0-9a-f]
      let shiftAmount = hexCharAsNumber(xd);
      let mask = op1 == "+" ? acc >>> shiftAmount : acc << shiftAmount;
      return op2 == "+" ? (acc + mask) & 0xffffffff : acc ^ mask;
    }, num);
  }
  function hexCharAsNumber(xd) {
    return xd >= "a" ? xd.charCodeAt(0) - 87 : Number(xd);
  }
  function transformQuery(query) {
    for (var e = [], f = 0, g = 0; g < query.length; g++) {
      let l = query.charCodeAt(g);
      if (l < 128) {
        e[f++] = l; //    0{l[6-0]}
      } else if (l < 2048) {
        e[f++] = (l >> 6) | 0xc0; //    110{l[10-6]}
        e[f++] = (l & 0x3f) | 0x80; //    10{l[5-0]}
      } else if (
        0xd800 == (l & 0xfc00) &&
        g + 1 < query.length &&
        0xdc00 == (query.charCodeAt(g + 1) & 0xfc00)
      ) {
        //    that's pretty rare... (avoid ovf?)
        l = (1 << 16) + ((l & 0x03ff) << 10) + (query.charCodeAt(++g) & 0x03ff);
        e[f++] = (l >> 18) | 0xf0; //    111100{l[9-8*]}
        e[f++] = ((l >> 12) & 0x3f) | 0x80; //    10{l[7*-2]}
        e[f++] = (l & 0x3f) | 0x80; //    10{(l+1)[5-0]}
      } else {
        e[f++] = (l >> 12) | 0xe0; //    1110{l[15-12]}
        e[f++] = ((l >> 6) & 0x3f) | 0x80; //    10{l[11-6]}
        e[f++] = (l & 0x3f) | 0x80; //    10{l[5-0]}
      }
    }
    return e;
  }
  function normalizeHash(encondindRound2) {
    if (encondindRound2 < 0) {
      encondindRound2 = (encondindRound2 & 0x7fffffff) + 0x80000000;
    }
    return encondindRound2 % 1e6;
  }
  function calcHash(query, windowTkk) {
    //    STEP 1: spread the the query char codes on a byte-array, 1-3 bytes per char
    let bytesArray = transformQuery(query);
    //    STEP 2: starting with TKK index, add the array from last step one-by-one, and do 2 rounds of shift+add/xor
    let d = windowTkk.split(".");
    let tkkIndex = Number(d[0]) || 0;
    let tkkKey = Number(d[1]) || 0;
    let encondingRound1 = bytesArray.reduce((acc, current) => {
      acc += current;
      return shiftLeftOrRightThenSumOrXor(acc, ["+-a", "^+6"]);
    }, tkkIndex);
    //    STEP 3: apply 3 rounds of shift+add/xor and XOR with they TKK key
    let encondingRound2 =
      shiftLeftOrRightThenSumOrXor(encondingRound1, ["+-3", "^+b", "+-f"]) ^
      tkkKey;
    //    STEP 4: Normalize to 2s complement & format
    let normalizedResult = normalizeHash(encondingRound2);
    return normalizedResult.toString() + "." + (normalizedResult ^ tkkIndex);
  }

这个calcHash就是加密入口,第一个参数是需要合成的文字,第二个其实是一个常量,就是互相约定的一个字符串。 所以,有了这个,我们就可以开心的使用google语音合成的功能啦。

后期我会把这个项目开源到github上,敬请期待!!!

Read More