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:
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:
* 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:
* 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:
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:
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:
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:
* 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:
.\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:
.\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:
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:
return 'Digest ' .. table.concat(digest, ', ')
With:
Code:
return 'Digest ' .. table.concat(digest, ',')

2. Modify the items and their order in the header.
Replace:
Code:
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:
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:
local cnonce = string.format('%08x', os.time())
With:
Code:
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:
return 'Digest ' .. table.concat(digest, ', ')
With:
Code:
return 'Digest ' .. table.concat(digest, ',')

2. Modify the items and their order in the header.
Replace:
Code:
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:
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:
local cnonce = string.format('%08x', os.time())
With:
Code:
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:
* 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:
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 Configuration → System → Security → Authentication 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: