Marcel Proust和Markov解密安全服务的T9文本


11

好像这个挑战可能在本质上是更多的Python风格 ... 不需要先验的Markov链或加密技术知识。

您是间谍,需要从英国安全服务M1S获得一些关键信息。M1S的代理很清楚,他们的Wi-Fi信号可以被拦截,利用了他们的Android / iOS安全漏洞等,因此他们所有人都使用Nokia 3310来传输使用T9自动完成功能键入的文本信息。

您以前曾破解过要交付给情报机构的电话,并在光荣的塑料键盘下安装了键盘记录程序,所以现在您收到的数字序列与他们键入的字母相对应,因此“ 鹰离开了巢穴提醒代理商

84303245304270533808430637802537808430243687

可是等等!某些T9序列含糊不清(“ 6263”可能是“名称”,“鬃毛”或“双簧管”;模糊性越大,可疑性就越大!),那么您该怎么办?您知道,M1S唯一使用的入学考试是在15秒内总结Marcel Proust的杰作“回忆过去的事”,因此您要根据整个主厨中的频率分布来选择上一个单词之后的单词。普鲁斯特的魅力!

您可以破解代码并获取原始消息吗?

T9的原理

代理商使用的诺基亚3310键盘

T9自动完成机制可以描述如下。如上图所示,它将字母字符映射为数字。

abc     -> 2
def     -> 3
ghi     -> 4
jkl     -> 5
mno     -> 6
pqrs    -> 7
tuv     -> 8
wxyz    -> 9
<space> -> 0
<other> -> <is deleted>

T9解密器接收到一个数字序列,并尝试猜测可以通过这些按键键入的单词。它可能使用标准的频率表,但我们要进一步走一步,并使用马尔可夫链预测下一个单词!

学习样本

该语料库是这严重剥夺普鲁斯特的“追忆似水年华”的版本s/-/ /gs/['’]s //gs/[^a-zA-Z ]//g-滚开迷惑的占有欲's!)最初发表的对阿德莱德大学的网站(这项工作的文本是在公共领域在澳洲)。

必须将整个文本分析为一个字符串,一个长句子,一个单词长矢量(以您的语言更方便),去除换行符在空格处拆分为单词。(我不提供单段文件,因为github工具可能会对此表示反对。)

如何将整个文本看成一个字符串/句子?R中的一个例子:

p_raw  <- read.table("proust.txt", sep="\t") # Because there are no tabs
p_vec  <- as.character(p_raw$V1)       # Conversion to character vector
p_str  <- paste(p_vec, collapse=" ")   # One long string with spaces
p_spl  <- strsplit(p_str, split=" ")[[1]] # Vector of 1360883 words
proust <- p_spl[p_spl!=""]           # Remove empty entries — 1360797

任务

给定一个数字序列作为数字,返回一个可能的文本字符串,该字符串可以使用概率链使用相应的T9键键入,以根据被视为一个长句子的该训练文本来预测下一个单词X。

如果X是文本的第一个T9单词,并且有多个猜测,则随机选择一个,否则仅选择一个。

对于所有后续的T9单词X(i)之前已解密的单词w(i-1)

  1. 如果可以用一种独特的方式将T9单词X转换为普通单词x,请执行此操作。
  2. 如果X有多个可用的转换选项,例如x1,x2,...,请查找前面的猜词w
    • 如果w在Proust的原始作品中从未跟随任何映射到X的东西,请随机选择任何可能的x1,x2,...。
    • 如果w X始终与原始x中的w x1相对应,并且没有可以映射到X中的并发xi,则选择x1
    • 如果宽x可以转换为w ^ X1w ^ X2,...可以在语料库中发现,再算上所有可能的是后续W¯¯并映射到X在语料库和挑的概率XI /(X1 + x2 + ...)

实施例2a。如果消息是76630489,则where 489可能是guyor ivy(它们在语料库中至少出现一次)7663可以被解读为some(一个非常可能的第一个单词)。如果some从未489在语料库中映射到任何内容,则以0.5的概率随机选择guyivy

实施例2b。如果消息是766302277437,那里2277437可能是barriercarrier7663可以被破译的some。如果Proust总是使用some carrier并且从不使用some barrier,那么选择some carrier

实施例2c。假设您要解密序列5363076635363被预测为lend7663可以是任何这些:pondroofsome。您可以计算lend示例语料库中后续单词的出现次数。假设您得到这样的信息(只是为了说明):

        T9  Word following lend  Occurrences
      7663  some                           7
      7663  pond                           2
      7663  roof                           1

因此,如果7663在此之前lend,则有分别代表20%和10%的7/(7+2+1)=70%概率。您的算法应在70%的情况下,在20%的情况下等产生。7663somepondrooflend somelend pond

您可以放心地假定代理使用az字母和空格(不带标点符号,所有格's和数字)。

您可能还假设M1S的代理人永远不会使用“回忆过去的事”(这是一个多达29,237个单词的巨大词汇!)范围之外的单词。

T9功能在此挑战中得到了实现,因此您可以看一下。

如果您需要任何帮助,则可以很轻松地在以及随后的挑战中驯服概率链,但是您甚至不需要了解这种链的原理:任务中已陈述了一切。

测试用例

--Inputs--
20784250276960369
20784250276960369
84303245304270533808430637802537808430243687
94280343084306289072908608430262780482737
94280343084306289072908608430262780482737

--Possible outputs--
c quick brown fox
a stick crown fox
the eagle gas left the nest blest vie agents
what did the navy pay to the coast guards
what did the navy raz un the coast guards

规则:

  • 有标准漏洞
  • 您不知道原始消息,您所得到的只是一个数字序列和文件proust.txt,您只需要将该文件加载到内存/工作空间/任何内容中。不需要任何独立的东西。假设proust.txt始终可以访问。
  • 如果根据语料库可能有多个解密选项,您的算法必须能够产生具有不同概率的不同输出(请参见示例2c)。

您需要保持谨慎,以最短的代码为准!

PS这种概率算法的明显好处是,对于歧义破译的字符串而言,获得真正的原始字符串的概率趋向于一个-稍等...

PPS 另请参见部分匹配预测


彼得·泰勒(Peter Taylor)在沙盒中讲话也已考虑在内。遗憾的是,尽管有多个更新,但很少有人在一周内回复该帖子,因此欢迎提出任何建议!顺便说一句,这是我的第一个挑战!
安德烈KOSTYRKA

我怀疑您没有得到很多答复的主要原因是理解该问题所需的高级知识。如果你正在想这个挑战,以吸引更大的人群,我建议包括显示了马尔可夫链在工作:)一些早期的例子
弥敦道美林

@NathanMerrill好吧,我添加了3个链接来示例挑战。但是,用户根本不需要知道马尔可夫链,因为该任务在问题主体中尽可能用算法描述:如果为X,则用在此学习样本中计算Z所获得的概率来做Y。我试图使它自给自足,因为它得到...
安德烈KOSTYRKA

噢,我明白。如果您不解释,我将投票关闭它。它只是看起来像它需要先进的知识:)
弥敦道美林

1
我喜欢这个挑战,但我还没有时间坐下来创建/寻求解决方案。希望那会很快发生。
Mego 2016年

Answers:


1

R解决方案,无竞争力的说明

首先,我们将单词序列加载到内存中:

p_raw  <- read.table("proust.txt", sep="\t") # Because there are no tabs
p_vec  <- as.character(p_raw$V1)       # Conversion to character vector
p_str  <- paste(p_vec, collapse=" ")   # One long string with spaces
p_spl  <- strsplit(p_str, split=" ")[[1]] # Vector of 1360883 words
proust <- p_spl[p_spl!=""]           # Remove empty entries — 1360797

其次,我们需要一个T9可以修饰任何文本的函数:

t9 <- function (x) {
  x <- chartr(paste(c(letters, " "), collapse=""), "222333444555666777788899990", tolower(x))
  x <- gsub("[^0-9]", "", x, perl = TRUE) # Safety check
  x <- x[x!=""] # Also for safety because... you know...
  x
}

然后,我们对Proust进行T9验证:

p9 <- t9(proust)

最终准备:我们使用调用的函数将输入字符串分割为零prep

prep <- function (x) {
  x <- chartr("0", " ", x)
  x <- strsplit(x, " ")[[1]]
  x <- x[x!=""] # Boil the empty strings for safety
  x
}

现在,我提出了一个函数,该函数接受任何输入的数字字符串,prep然后将其逐个解密:

decip <- function(x, verbose = FALSE) {
  x <- prep(x)
  l <- length(x)
  decrypted <- rep(NA, l)
  tb <- table(proust[which(p9 == x[1])])
  decrypted[1] <- sample(names(tb), 1, prob=tb/sum(tb))
  if (verbose) print(decrypted[1])
  for (i in 2:l) {
    mtchl <- p9 == x[i]
    mtch <- which(mtchl)  # Positions that matched
    pmtch <- proust[mtch] # Words that matched
    tb <- table(pmtch)    # Count occurrences that matched
    if (length(tb)==1) {  # It is either 1 or >1
      decrypted[i] <- names(tb)[1]
      if (verbose) print(paste0("i = ", i, ", case 1: unique decryption"))
      } else {  # If there are more than one ways to decipher...
      preced <- proust[mtch-1] 
      s <- sum(preced==decrypted[i-1])
      if (s==0) {
        decrypted[i] <- sample(names(tb), 1)
        if (verbose) print(paste0("i = ", i, ", case 2a: multiple decryption, collocation never used, picking at random"))
        } else {
        tb2 <- table(pmtch[preced==decrypted[i-1]])
        if (length(tb2)==1) {
          decrypted[i] <-  names(tb2)[1]
          if (verbose) print(paste0("i = ", i, ", case 2b: multiple decryption, only one collocation found, using it"))
        } else {
          decrypted[i] <- sample(names(tb2), 1, prob = tb2/sum(tb2))
          if (verbose) print(paste0("i = ", i, ", case 2c: multiple decryption, ", length(tb2), " choices"))
          }
      }
    }
    if(verbose) print(decrypted[i])
  }
  decrypted
}

现在它实际上在做什么:

decip("20784250276960369", verbose=TRUE)
----
[1] "a"
[1] "i = 2, case 2c: multiple decryption, 2 choices"
[1] "quick"
[1] "i = 3, case 2a: multiple decryption, collocation never used, picking at random"
[1] "brown"
[1] "i = 4, case 1: unique decryption"
[1] "fox"
[1] "a"     "quick" "brown" "fox" 

第二个例子:

decip("84303245304270533808430637802537808430243687", verbose=TRUE)
----
[1] "what"
[1] "i = 2, case 2b: multiple decryption, only one collocation found, using it"
[1] "did"
[1] "i = 3, case 2b: multiple decryption, only one collocation found, using it"
[1] "the"
[1] "i = 4, case 1: unique decryption"
[1] "navy"
[1] "i = 5, case 2a: multiple decryption, collocation never used, picking at random"
[1] "raz"
[1] "i = 6, case 2a: multiple decryption, collocation never used, picking at random"
[1] "um"
[1] "i = 7, case 2a: multiple decryption, collocation never used, picking at random"
[1] "the"
[1] "i = 8, case 2b: multiple decryption, only one collocation found, using it"
[1] "coast"
[1] "i = 9, case 1: unique decryption"
[1] "guards"
[1] "what"   "did"    "the"    "navy"   "raz"    "um"     "the"    "coast"  "guards"

请不要评论这可以打高尔夫球。由于我的糟糕程度,似乎很少有人对此挑战感兴趣,因此我发布了此答案以显示可能的程序的外观。您无需对此答案进行投票。


1

Python 3,316个字节

from random import*
from collections import*
def d(s,f):
 D=defaultdict(Counter);p=q=''
 for w in open(f).read().split():D[w.translate({97+c:(c-(c>17)-(c>24))//3+50for c in range(26)})].update([w]);D[p].update([w]);p=w
 for c in s.split('0'):q=choice([*(len(D[c])>1and D[c]&D[q]or D[c]).elements()]);print(q,end=' ')
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.