ncrypt 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. #!/usr/bin/ruby
  2. require 'openssl'
  3. require 'base64'
  4. require 'securerandom'
  5. require 'net/http'
  6. require 'net/https'
  7. def pbkdf2(password, salt, keylen, opts = {})
  8. hash = opts[:hash] || 'sha1'
  9. iterations = (opts[:iterations] || 1) - 1
  10. def bigendian(val, len)
  11. len.times.map { val, r = val.divmod 256; r }.reverse.pack('C*')
  12. end
  13. key = ''
  14. blockindex = 1
  15. while key.length < keylen
  16. block = OpenSSL::HMAC.digest(hash, password, salt + bigendian(blockindex, 4))
  17. u = block
  18. iterations.times do
  19. u = OpenSSL::HMAC.digest(hash, password, u)
  20. block.length.times.each do |j|
  21. block[j] ^= u[j]
  22. end
  23. end
  24. key += block
  25. blockindex += 1
  26. end
  27. key.slice(0, keylen)
  28. end
  29. def aes_decrypt(text, key, opts = {})
  30. text = text.dup
  31. iv = text.slice!(0, 16) # aes has fixed blocksize of 128 bits
  32. key = pbkdf2(key, iv, (opts[:aeskeysize] || 256) / 8, opts)
  33. cipher = OpenSSL::Cipher::AES.new(8 * key.length, opts[:mode] || :OFB).decrypt
  34. cipher.key = key
  35. cipher.iv = iv
  36. cipher.update(text) + cipher.final
  37. end
  38. def aes_encrypt(text, opts = {})
  39. key = opts[:key] || generateKey(opts[:keylen] || 24)
  40. cipher = OpenSSL::Cipher::AES.new(opts[:aeskeysize] || 256, opts[:mode] || :OFB).encrypt
  41. cipher.iv = iv = opts[:iv] || cipher.random_iv
  42. cipher.key = pbkdf2(key, iv, (opts[:aeskeysize] || 256) / 8, opts)
  43. text = cipher.update(text) + cipher.final
  44. [ iv + text, key ]
  45. end
  46. def generateKey(len = 24)
  47. chars = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
  48. len.times.map { chars[SecureRandom.random_number(chars.length)].ord }.pack('c*')
  49. end
  50. def decrypt(text, key, opts = {})
  51. text = Base64.decode64(text)
  52. aes_decrypt(text, key, opts)
  53. end
  54. def encrypt(text, opts = {})
  55. text, key = aes_encrypt(text, opts)
  56. [ Base64.encode64(text).gsub(/\s+/, ""), key ]
  57. end
  58. def fixipv6host(host)
  59. if m = /^\[([0-9a-fA-F:]+)\]$/.match(host)
  60. return m[1]
  61. end
  62. host
  63. end
  64. def http_get(uri)
  65. request = Net::HTTP::Get.new uri.request_uri
  66. request['Host'] = uri.host
  67. http = Net::HTTP.new(fixipv6host(uri.host), uri.port)
  68. http.use_ssl = uri.scheme == 'https'
  69. # http.verify_mode = OpenSSL::SSL::VERIFY_PEER
  70. # http.verify_depth = 5
  71. http.verify_mode = OpenSSL::SSL::VERIFY_NONE
  72. http.start { |http| http.request request }
  73. end
  74. def http_post(uri, args)
  75. request = Net::HTTP::Post.new(uri.request_uri)
  76. request['Host'] = uri.host
  77. request.set_form_data(args)
  78. http = Net::HTTP.new(fixipv6host(uri.host), uri.port)
  79. http.use_ssl = uri.scheme == 'https'
  80. # http.verify_mode = OpenSSL::SSL::VERIFY_PEER
  81. # http.verify_depth = 5
  82. http.verify_mode = OpenSSL::SSL::VERIFY_NONE
  83. http.start { |http| http.request request }
  84. end
  85. def hashpw(password)
  86. return nil if password.nil?
  87. return OpenSSL::Digest::SHA1.hexdigest(password)
  88. end
  89. require 'optparse'
  90. opts = {}
  91. OptionParser.new do |o|
  92. o.on('-u', '--url URL', "Retrieve paste from url (conflicts with the posting options)") { |url| opts[:url] = URI(url) }
  93. o.on('-f', '--file FILENAME', "Upload file") { |fn| opts[:fn] = fn }
  94. o.on('-m', '--mime MIMETYPE', "Specify mime type for paste") { |mime| opts[:mime] = mime }
  95. o.on('-t', '--ttl TTL', "Specify Time-To-Live for paste in seconds, default one week (-1 for indefinately, -100 for one time only)") { |ttl| opts[:ttl] = ttl }
  96. o.on('-p', '--password[PASSWORD]', "Use password protection on server side (no additional encryption)") { |password|
  97. opts[:pass] = true
  98. opts[:password] = password unless password.nil?
  99. }
  100. o.on('-s', '--site SITE', "Post upload to another ncrypt pastebin (default: https://ncry.pt)") { |s| opts[:site] = s }
  101. o.separator ""
  102. o.separator " If neither url nor filename was given, a final parameter can be used to specify it. Urls are autodetected."
  103. o.separator ""
  104. o.on('-h', '--help', "Show this help") { STDERR.puts o; exit }
  105. o.parse!
  106. if ARGV.length == 1
  107. if opts[:url] || opts[:fn]
  108. STDERR.puts o
  109. exit 1
  110. end
  111. begin
  112. url = URI(ARGV[0])
  113. if ("http" == url.scheme || "https" == url.scheme) && url.host
  114. opts[:url] = url
  115. else
  116. opts[:fn] = ARGV[0]
  117. end
  118. rescue
  119. opts[:fn] = ARGV[0]
  120. end
  121. end
  122. if ARGV.length > 1 or (opts[:url] and (opts[:fn] || opts[:ttl] || opts[:mime] || opts[:site] || opts[:pass])) or (!opts[:url] and !opts[:fn])
  123. STDERR.puts o
  124. exit 1
  125. end
  126. end
  127. if opts[:pass] and opts[:password].nil?
  128. if opts[:fn] == '-'
  129. STDERR.puts "Can't read post data from stdin and prompt for password"
  130. exit 1
  131. end
  132. STDERR.write "Enter password: "
  133. STDERR.flush
  134. opts[:password] = STDIN.readline.chomp
  135. end
  136. if opts[:url]
  137. uri = opts[:url]
  138. if !uri.fragment
  139. STDERR.puts "Specified url has no fragment, cannot decode paste"
  140. exit 1
  141. end
  142. password = nil
  143. while
  144. if password.nil?
  145. resp = http_get(uri)
  146. else
  147. resp = http_post(uri, :p => hashpw(password))
  148. end
  149. if 200 != resp.code.to_i
  150. STDERR.puts "Got HTTP/#{resp.http_version} #{resp.code} #{resp.message}"
  151. STDERR.puts "Location: #{resp['Location']}" if resp['Location']
  152. exit 1
  153. end
  154. html = resp.body
  155. if m = (/<input type="hidden" name="data" id="data" value="([^"]+)"/m.match(html) || (!password.nil? && /"data":"([^"]+)"/m.match(html)))
  156. cipher = m[1]
  157. STDOUT.write decrypt(cipher, uri.fragment)
  158. exit 0
  159. elsif html =~ /<div id="askpassword">/ and password.nil?
  160. STDERR.write "Paste is password protected. Enter password: "
  161. STDERR.flush
  162. password = STDIN.readline.chomp
  163. else
  164. STDERR.puts "Can't parse response."
  165. exit 1
  166. end
  167. end
  168. else
  169. uri = URI(opts[:site] || 'https://ncry.pt')
  170. if opts[:fn] != '-'
  171. if opts[:mime].nil?
  172. begin
  173. opts[:mime] = IO.popen("file --brief --mime-type '#{opts[:fn]}'", "r").read.chomp
  174. rescue
  175. # use default mime type text/plain
  176. end
  177. end
  178. text = File.open(opts[:fn], "rb").read
  179. else
  180. text = STDIN.read
  181. end
  182. cipher, key = encrypt(text)
  183. resp = http_post(uri, :data => cipher, :syn => opts[:mime] || 'text/plain', :ttl => opts[:ttl] || (7*86400), :p => hashpw(opts[:password]))
  184. if 200 != resp.code.to_i
  185. STDERR.puts "Key is #{key}"
  186. STDERR.puts "Got HTTP/#{resp.http_version} #{resp.code} #{resp.message}"
  187. resp.each_header { |k,v| STDERR.puts "#{k}: #{v}" }
  188. STDERR.puts resp.body
  189. exit 1
  190. end
  191. html = resp.body
  192. if html =~ /^\{"id":".*"\}\s*$/m then
  193. id = html.gsub(/^\{"id":"/m, "").gsub(/"\}\s*$/m, "")
  194. uri.path += '/' unless ?/ == uri.path[-1]
  195. uri.path += id
  196. uri.fragment = key
  197. puts uri
  198. else
  199. STDERR.puts "Key is #{key}"
  200. STDERR.puts "Can't parse response #{resp.body}"
  201. exit 1
  202. end
  203. end