From: Ryan Leavengood Date: 2005-06-05T21:50:54+09:00 Subject: [SOLUTION] Whiteout (#34) This was a fun one. If I would consider anything my Ruby forte, text processing would be it. So this was right up my alley. I learned a good bit too. For example, Fixnum#to_s can take a radix representing the base you want the number converted to in the String. String#to_i does the same thing, just in the opposite direction. I first wrote a simple binary conversion that was inspired by what I could figure out from the original Perl ACME::Bleach (which wasn't too much since I'm not a Perl hacker and it was somewhat obfuscated.) Then I thought I could probably one-up that by making a ternary conversion. I considered trying higher radixes, but found at least on my editor (Vim) that only spaces, tabs and newlines were truly "invisible." So ternary it was, as shown in the code below. Since I had written two conversions, I decided to make things interesting and randomly choose which one I used when creating the files. That should thoroughly confuse people who try and decode any files that have been "whited out" without knowing the code :) Anyhow, here is the code (if this weren't a Ruby Quiz I would make this code much more compact and obfuscated): # Ruby Quiz: Whiteout (#34) # Solution by Ryan Leavengood # # There are two ways of "whiting out", one that uses a binary # encoding of spaces and tabs on each line (preserving the # original newlines), and a ternary encoding that makes newlines # part of the code and encodes any of the original newlines. The # method of encoding is chosen at random. In theory other # non-printable characters could be added to increase the radix # used for encoding, but I think the best cross-platform "whiting # out" can be had using spaces, tabs and newlines. REQUIRE_LINE = "require 'whiteout'" class WhiteoutBinary attr_reader :id WHITESPACE = " \t" DIGITS = '01' def initialize @id = " \t\t" end def paint_on(paper) paper.map do |line| line.chomp.unpack('b*')[0].tr(DIGITS, WHITESPACE) end.join("\n") end def rub_off(paper) paper.map do |line| [line.chomp.tr(WHITESPACE, DIGITS)].pack('b*') end.join("\n") end end class WhiteoutTernary attr_reader :id WHITESPACE = " \t\n" DIGITS = '012' # This allows up to 22222 ternary, which is 242 decimal, enough # for most of ASCII DIGIT_LENGTH = 5 RADIX = 3 def initialize @id = " \t\t\t" end def paint_on(paper) paper.join.gsub(/./m) do |c| c[0].to_s(RADIX).rjust(DIGIT_LENGTH,'0') end.tr(DIGITS, WHITESPACE) end def rub_off(paper) paper.join.tr(WHITESPACE, DIGITS).gsub(/.{#{DIGIT_LENGTH}}/) do |d| d.to_i(RADIX).chr end end end bottle_holder = [WhiteoutBinary.new, WhiteoutTernary.new] if $0 == __FILE__ ARGV.each do |filename| wo_name = "#{filename}.wo" File.open(wo_name, 'w') do |file| whiteout = bottle_holder[rand(2)] paper = IO.readlines(filename) if paper[0] =~ /^\s*#!/ file.print paper.shift end file.puts REQUIRE_LINE file.puts whiteout.id file.print whiteout.paint_on(paper) end File.rename(filename, filename+'.bak') File.rename(wo_name, filename) end else paper = IO.readlines($0) paper.shift if paper[0] =~ /^\s*#!/ paper.shift if paper[0] =~ /^#{REQUIRE_LINE}/ id = paper.shift.chomp whiteout = bottle_holder.find {|bottle| bottle.id == id} if whiteout eval whiteout::rub_off(paper) else puts "Error: This does not appear to be a valid whiteout file!" exit(1) end end __END__ Ryan Leavengood