This forum uses cookies
This forum makes use of cookies to store your login information if you are registered, and your last visit if you are not. Cookies are small text documents stored on your computer; the cookies set by this forum can only be used on this website and pose no security risk. Cookies on this forum also track the specific topics you have read and when you last read them. Please confirm that you accept these cookies being set.

Get Snapshot from Hikvision Camera
#1
Hi,

I´ve tried to get snapshot with socket http request

Code:
1234567891011121314
function SnapshotCamaraIP2()     local IP = "192.168.1.100"   local UrlCamara = 'http://user:pass@'..IP..'/ISAPI/Streaming/channels/101/picture'    local response          require('socket.http')   socket.http.TIMEOUT = 5   response,err = socket.http.request(UrlCamara)     log(response,err)   return response   end

And response is:

Code:
12345678910
* arg: 1   * string: <!DOCTYPE html> <html><head><title>Document Error: Unauthorized</title></head> <body><h2>Access Error: 401 -- Unauthorized</h2> <p>Authentication Error</p> </body> </html> * arg: 2   * number: 401

If I put this url in explorer it works.

Thanks
Reply
#2
Are you sure that this uses Basic auth but not digest?
Reply
#3
(13.03.2023, 12:51)admin Wrote: Are you sure that this uses Basic auth but not digest?

I´ll try it with digest first. I wroute in the post "Email with attachement" but you tell me to try with basic auth. But this post is very old and I create a new more specific.

Trying with digest auth this is the result:

Code:
1234567891011121314151617181920212223242526272829303132
* arg: 1   * string: <!DOCTYPE html> <html><head><title>Document Error: Unauthorized</title></head> <body><h2>Access Error: 401 -- Unauthorized</h2> <p>Authentication Error</p> </body> </html> <!DOCTYPE html> <html><head><title>Document Error: Unauthorized</title></head> <body><h2>Access Error: 401 -- Unauthorized</h2> <p>Authentication Error</p> </body> </html> * arg: 2   * number: 401 * arg: 3   * table:    ["server"]     * string: webserver    ["content-type"]     * string: text/html    ["connection"]     * string: close    ["date"]     * string: Mon, 13 Mar 2023 10:37:01 GMT    ["x-frame-options"]     * string: SAMEORIGIN    ["www-authenticate"]     * string: Digest qop="auth", realm="IP Camera(F0849)", nonce="5a6d566c5a475a6d59324d365a474534596a67794d6d493d", stale="FALSE"    ["content-length"]     * string: 178

And the code is:

Code:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
local url = 'http://user:Pass@'..IP..'/ISAPI/Streaming/channels/101/picture' function SnapshotCamaraIP (url)     --Meter url de la camara http://admin:pass@"IP"/snapshot.jpeg   local skthttp = require('socket.http')   local skturl = require('socket.url')   local ltn12 = require('ltn12')   local md5sum = require('encdec').md5   local hash = function(...)     return md5sum(table.concat({...}, ':'))     end   local parse_header = function(header)     local result = {}     for key, value in (header .. ','):gmatch('(%w+)=(.-),') do       if value:sub(1, 1) == '"' then -- strip quotes         result[ key:lower() ] = value:sub(2, -2)       else         result[ key:lower() ] = value       end     end     return result             end     local make_digest_header = function(headers)       local digest = {}       for _, header in ipairs(headers) do         if not header.unquote then           header[ 2 ] = '"' .. header[ 2 ] .. '"'         end         digest[ #digest + 1 ] = header[ 1 ] .. '=' .. header[ 2 ]       end       return 'Digest ' .. table.concat(digest, ', ')     end local _request = function(req)   if not req.url then     return nil, 'missing url'   end   local url = skturl.parse(req.url)   local user, password = url.user, url.password     --log(user,password)   if not user or not password then     return nil, 'missing credentials in url'   end   url.user, url.password, url.authority, url.userinfo = nil, nil, nil, nil   req.url = skturl.build(url)   local source   if req.source then     local chunks = {}     local capture = function(chunk)       if chunk then         chunks[ #chunks + 1 ] = chunk       end       return chunk     end     local chunk_id = 0     source = function()       chunk_id = chunk_id + 1       return chunks[ chunk_id ]     end     req.source = ltn12.source.chain(req.source, capture)   end   local body, code, hdrs = skthttp.request(req)   --log(body, code, hdrs)   if code == 401 and hdrs['www-authenticate'] then     local ht = parse_header(hdrs['www-authenticate'])     log(ht)     if not ht.realm or not ht.nonce then       log("REALM")       return nil, 'missing realm/nonce from response'     end     if ht.qop ~= 'auth' then       log("QOP")       return nil, 'unsupported qop ' .. tostring(ht.qop)     end     if ht.algorithm and ht.algorithm:lower() ~= 'md5' then       log("MD5")       return nil, 'unsupported algo ' .. tostring(ht.algorithm)     end     local nc = '00000001'     local cnonce = string.format('%08x', os.time())     local uri = skturl.build({ path = url.path, query = url.query })     local method = req.method or 'GET'     local response = hash(       hash(user, ht.realm, password),       ht.nonce,       nc,       cnonce,       'auth',       hash(method, uri)     )     req.headers = req.headers or {}     local auth = {       { 'username', user },       { 'realm', ht.realm },       { 'nonce', ht.nonce },       { 'uri', uri },       { 'cnonce', cnonce },       { 'nc', nc, unquote = true },       { 'qop', 'auth' },       { 'algorithm', 'MD5' },       { 'response', response },     }     if ht.opaque then       table.insert(auth, { 'opaque', ht.opaque })     end     req.headers.authorization = make_digest_header(auth)     if not req.headers.cookie and hdrs['set-cookie'] then       -- not really correct but enough for httpbin       local cookie = (hdrs['set-cookie'] .. ';'):match('(.-=.-)[;,]')       if cookie then         req.headers.cookie = '$Version: 0; ' .. cookie .. ';'       end     end     if req.source then       req.source = source     end     body, code, hdrs = skthttp.request(req)   end   return body, code, hdrs end local request = function(url)   local t = type(url)   if t == 'table' then     return _request(table.clone(url))   elseif t == 'string' then     local req = {}     local _, code, headers = _request({ url = url, sink = ltn12.sink.table(req) })     return table.concat(req), code, headers   end end     image, err, hdrs = request(url)   log(image, err, hdrs)   return image,err end

Thanks
Reply
#4
There might be some minor difference in the digest auth between the camera and the script. But it's impossible to tell what the difference is. Use basic auth instead.
Reply
#5
(14.03.2023, 08:46)admin Wrote: There might be some minor difference in the digest auth between the camera and the script. But it's impossible to tell what the difference is. Use basic auth instead.

I don´t understand. I put in the first post that basic auth doesnt´t work. I put the code.
What shoud I try?

Thanks
Reply
#6
Some cameras allow passing credentials as a GET parameter. See if this works for you:
Code:
123456789
http = require('socket.http') base64enc = require('encdec').base64enc ip = '192.168.192.168' auth = 'user:pass' url = 'http://' .. ip .. '/ISAPI/Streaming/channels/101/picture?auth=' .. base64enc(auth) res, err = http.request(url) log(res, err)
Reply
#7
Hi,

for me this not working, just checking it.

log:

* arg: 1
* string: <!DOCTYPE html>
<html><head><title>Document Error: Unauthorized</title></head>
<body><h2>Access Error: 401 -- Unauthorized</h2>
<p>Authentication Error</p>
</body>
</html>

* arg: 2
* number: 401

Alex
Reply
#8
(14.03.2023, 16:35)admin Wrote: Some cameras allow passing credentials as a GET parameter. See if this works for you:
Code:
123456789
http = require('socket.http') base64enc = require('encdec').base64enc ip = '192.168.192.168' auth = 'user:pass' url = 'http://' .. ip .. '/ISAPI/Streaming/channels/101/picture?auth=' .. base64enc(auth) res, err = http.request(url) log(res, err)

Same error:

Code:
12345678910
* arg: 1   * string: <!DOCTYPE html> <html><head><title>Document Error: Unauthorized</title></head> <body><h2>Access Error: 401 -- Unauthorized</h2> <p>Authentication Error</p> </body> </html> * arg: 2   * number: 401
Reply
#9
See if this works with curl:
  1. Download curl for Windows: https://curl.se/windows/
  2. Open terminal, go to bin directory
  3. Run curl command, replace IP/USER/PASS as needed
  4. See if you get a correct camera snapshot as snapshot.jpeg in the bin directory
  5. If this works then provide the whole curl output that you get in the terminal window

Code:
1
.\curl.exe -v 'http://IP/ISAPI/Streaming/channels/101/picture' --digest -u USER:PASS -o snapshot.jpeg
Reply
#10
(15.03.2023, 08:21)admin Wrote: See if this works with curl:
  1. Download curl for Windows: https://curl.se/windows/
  2. Open terminal, go to bin directory
  3. Run curl command, replace IP/USER/PASS as needed
  4. See if you get a correct camera snapshot as snapshot.jpeg in the bin directory
  5. If this works then provide the whole curl output that you get in the terminal window

Code:
1
.\curl.exe -v 'http://IP/ISAPI/Streaming/channels/101/picture' --digest -u USER:PASS -o snapshot.jpeg

   
Reply
#11
Please provide it as text, not as a screenshot. Also send your camera password via PM so I can compare the results with the Lua library.
Reply
#12
(15.03.2023, 09:28)admin Wrote: Please provide it as text, not as a screenshot. Also send your camera password via PM so I can compare the results with the Lua library.

Code:
123456789101112131415161718192021222324252627282930313233343536373839404142
PS C:\Users\david.grandes\Downloads\curl-7.88.1_2-win64-mingw\bin> .\curl.exe -v 'http://192.168.2.154/ISAPI/Streaming/channels/101/picture' --digest -u admin:<PASS> -o snapshot.jpeg   % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current                                  Dload  Upload   Total   Spent    Left  Speed   0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying 192.168.2.154:80... * Connected to 192.168.2.154 (192.168.2.154) port 80 (#0) * Server auth using Digest with user 'admin' > GET /ISAPI/Streaming/channels/101/picture HTTP/1.1 > Host: 192.168.2.154 > User-Agent: curl/7.88.1 > Accept: */* > < HTTP/1.1 401 Unauthorized < Date: Wed, 15 Mar 2023 10:29:53 GMT < Server: webserver < X-Frame-Options: SAMEORIGIN < Content-Length: 178 < Content-Type: text/html < Connection: close < WWW-Authenticate: Digest qop="auth", realm="IP Camera(F0849)", nonce="5a575930597a67314d6a706c4e475178596a51784e673d3d", stale="FALSE" <   0   178    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0 * Closing connection 0 * Issue another request to this URL: 'http://192.168.2.154/ISAPI/Streaming/channels/101/picture' * Hostname 192.168.2.154 was found in DNS cache *   Trying 192.168.2.154:80... * Connected to 192.168.2.154 (192.168.2.154) port 80 (#1) * Server auth using Digest with user 'admin' > GET /ISAPI/Streaming/channels/101/picture HTTP/1.1 > Host: 192.168.2.154 > Authorization: Digest username="admin",realm="IP Camera(F0849)",nonce="5a575930597a67314d6a706c4e475178596a51784e673d3d",uri="/ISAPI/Streaming/channels/101/picture",cnonce="100632b1eb41b178e302a905c7861379",nc=00000001,response="945dfaa7af413e634b2a7070a45605f4",qop="auth" > User-Agent: curl/7.88.1 > Accept: */* > < HTTP/1.1 200 OK < Content-Type: image/jpeg; charset="UTF-8" < Connection: close < Content-Length:152478 < { [12240 bytes data] 100  148k  100  148k    0     0   201k      0 --:--:-- --:--:-- --:--:--  201k * Closing connection 1 PS C:\Users\david.grandes\Downloads\curl-7.88.1_2-win64-mingw\bin>
Reply
#13
Also send your camera password via PM so I can compare the results with the Lua library.
Reply
#14
(15.03.2023, 09:35)admin Wrote: Also send your camera password via PM so I can compare the results with the Lua library.

Done
Reply
#15
Thanks, here's a couple of things you can try:

1. Remove space between items in the Authorization header.
Replace:
Code:
1
return 'Digest ' .. table.concat(digest, ', ')
With:
Code:
1
return 'Digest ' .. table.concat(digest, ',')

2. Modify the items and their order in the header.
Replace:
Code:
1234567891011
local auth = {   { 'username', user },   { 'realm', ht.realm },   { 'nonce', ht.nonce },   { 'uri', uri },   { 'cnonce', cnonce },   { 'nc', nc, unquote = true },   { 'qop', 'auth' },   { 'algorithm', 'MD5' },   { 'response', response }, }
With:
Code:
1234567891011
local auth = {   { 'username', user },   { 'realm', ht.realm },   { 'nonce', ht.nonce },   { 'uri', uri },   { 'cnonce', cnonce },   { 'nc', nc, unquote = true },   -- { 'algorithm', 'MD5' },   { 'response', response },   { 'qop', 'auth' }, }

3. Make the cnonce longer.
Replace:
Code:
1
local cnonce = string.format('%08x', os.time())
With:
Code:
1
local cnonce = md5sum(string.format('%08x', os.time()))

When setting cnonce manually I get the same header as curl does by applying change 1 and 2.
Reply
#16
(15.03.2023, 10:55)admin Wrote: Thanks, here's a couple of things you can try:

1. Remove space between items in the Authorization header.
Replace:
Code:
1
return 'Digest ' .. table.concat(digest, ', ')
With:
Code:
1
return 'Digest ' .. table.concat(digest, ',')

2. Modify the items and their order in the header.
Replace:
Code:
1234567891011
local auth = {   { 'username', user },   { 'realm', ht.realm },   { 'nonce', ht.nonce },   { 'uri', uri },   { 'cnonce', cnonce },   { 'nc', nc, unquote = true },   { 'qop', 'auth' },   { 'algorithm', 'MD5' },   { 'response', response }, }
With:
Code:
1234567891011
local auth = {   { 'username', user },   { 'realm', ht.realm },   { 'nonce', ht.nonce },   { 'uri', uri },   { 'cnonce', cnonce },   { 'nc', nc, unquote = true },   -- { 'algorithm', 'MD5' },   { 'response', response },   { 'qop', 'auth' }, }

3. Make the cnonce longer.
Replace:
Code:
1
local cnonce = string.format('%08x', os.time())
With:
Code:
1
local cnonce = md5sum(string.format('%08x', os.time()))

When setting cnonce manually I get the same header as curl does by applying change 1 and 2.

It doesn´t work.

Code:
12345678910111213
* arg: 1   * string: <!DOCTYPE html> <html><head><title>Document Error: Unauthorized</title></head> <body><h2>Access Error: 401 -- Unauthorized</h2> <p>Authentication Error</p> </body> </html> <!DOCTYPE html> <html><head><title>Document Error: Unauthorized</title></head> <body><h2>Access Error: 401 -- Unauthorized</h2> <p>Authentication Error</p> </body> </html>

I´ve found in the camera manual (Hikvision HWI-B140H (https://www.hi-watch.eu/en-us/product/15...let-camera))
The autentification mode is:

Code:
123456789101112131415161718192021222324252627
9.17 Security You can improve system security by setting security parameters. 9.17.1 Authentication You can improve network access security by setting RTSP and WEB authentication. Go to ConfigurationSystemSecurityAuthentication to choose authentication protocol and method according to your needs. RTSP Authentication Digest and digest/basic are supported, which means authentication information is needed when RTSP request is sent to the device. If you select digest/basic, it means the device supports digest or basic authentication. If you select digest, the device only supports digest authentication. RTSP Digest Algorithm MD5, SHA256 and MD5/SHA256 encrypted algorithm in RTSP authentication. If you enable the digest algorithm except for MD5, the third-party platform might not be able to log in to the device or enable live view because of compatibility. The encrypted algorithm with high strength Network Camera User Manual 79 is recommended. WEB Authentication Digest and digest/basic are supported, which means authentication information is needed when WEB request is sent to the device. If you select digest/basic, it means the device supports digest or basic authentication. If you select digest, the device only supports digest authentication. WEB Digest Algorithm MD5, SHA256 and MD5/SHA256 encrypted algorithm in WEB authentication. If you enable the digest algorithm except for MD5, the third-party platform might not be able to log in to the device or enable live view because of compatibility. The encrypted algorithm with high strength is recommended.
Reply
#17
Can you provide remote access to the LM that can access the camera via ZeroTier? I'll send you my network id via PM.
Reply
#18
Very interesting topic. Subscribing.
Reply
#19
The conclusion is that the digest auth script on LM is working correctly. It is recommended to create separate user that only has viewing access on the camera. It seems that the camera might block access for some reason (might be a different script with wrong credentials). But this has nothing to do with LM. Camera firmware upgrade might solve this issue.
Reply


Forum Jump: